package com.atwisdom.star.core.bsl.planner.Impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.atwisdom.star.common.util.DateUtils;
import com.atwisdom.star.common.util.TypeUtils;
import com.atwisdom.star.common.vo.*;
import com.atwisdom.star.core.bsl.customized.impl.PurchaseBslServiceImpl;
import com.atwisdom.star.core.bsl.modelClass.logic.LogicInstanceBslService;
import com.atwisdom.star.core.bsl.planner.PlannerBslService;
import com.atwisdom.star.core.bsl.planner.entity.*;
import com.atwisdom.star.core.dao.pojo.modelClass.hier.HierInstance;
import com.atwisdom.star.core.dao.pojo.modelClass.logic.LogicInstance;
import com.atwisdom.star.core.dsl.functionClass.Impl.BatchNumberDslServiceImpl;
import com.atwisdom.star.core.dsl.modelClass.hier.HierInstanceDslService;
import com.atwisdom.star.core.dsl.modelClass.logic.Impl.LogicInstanceDslServiceImpl;
import com.atwisdom.star.exception.pojo.BusinessException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.*;

@Service
public class PlannerBslServiceImpl implements PlannerBslService {
    @Autowired
    private LogicInstanceDslServiceImpl logicInstanceDslService;

    @Autowired
    private HierInstanceDslService hierInstanceDslService;

    @Autowired
    private MrpLogBslServiceImpl mrpLogBslService;

    @Autowired
    private SysConfigBslServiceImpl sysConfigBslService;

    @Autowired
    private MaterialBslServiceImpl materialBslService;

    @Autowired
    private ProductBomBslServiceImpl productBomBslService;

    @Autowired
    private StockDetailBslServiceImpl stockDetailBslService;

    @Autowired
    private WorkProcessPathBslServiceImpl workProcessPathBslService;

    @Autowired
    private SaleOrderBslServiceImpl saleOrderBslService;

    @Autowired
    private EquipmentOccupyBslServiceImpl equipmentOccupyBslService;

    @Autowired
    private MrpBslServiceImpl mrpBslService;

    @Autowired
    private BatchNumberDslServiceImpl batchNumberDslService;

    @Autowired
    private WorkCalendarBslServiceImpl workCalendarBslService;

    @Autowired
    private MonthPlanBslServiceImpl monthPlanBslService;

    @Autowired
    private PurchaseBslServiceImpl purchaseBslServiceImpl;

    @Autowired
    private LogicInstanceBslService logicInstanceBslService;

    @Value("${mrp.effective-time}")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDate effectiveTime;

    //计划类型  订单,内示
    private String planType = "订单";

    //计划排程生成最终的数据集
    List<ProductBomSummary> productPlanSummaryList;

    @Override
    @Transactional
    public ResultInfo<?> execSalePlanMRP(JSONObject dataObj) {
        productPlanSummaryList = new ArrayList<>();
        int mrpId = dataObj.getInteger("mrpId");
        this.mrpLogBslService.initMrpLog(mrpId);
        this.sysConfigBslService.initSysConfig();
        this.materialBslService.initMaterial();
        this.productBomBslService.initBOM();
        this.stockDetailBslService.initMrpStock();
        this.workProcessPathBslService.initWorkProcessStat();
        this.saleOrderBslService.initSalePlanMRP(dataObj);
        this.workCalendarBslService.initHoliday();
        try {
            this.deletePlanOrder();
            //初始化设备占用
            this.equipmentOccupyBslService.initEquipmentOccupyBySaleOrder();
            //计划类型
            this.planType = "订单";
            //开始记录日志
            System.out.println("每日销售订单计划排程开始执行!");
            this.mrpLogBslService.buildMrpLog(1, "每日销售订单计划排程开始执行!");

//            if (CollectionUtil.isNotEmpty(this.saleOrderBslService.getSaleOrders())) {
            //处理销售订单计划与排程
            this.dealSalePlanMRP();
            this.updateSaleOrders();
//            } else {
//                this.mrpLogBslService.buildMrpLog(2, "没有查到销售订单计划排程执行结束!");
//            }
            //完成记录日志
            this.mrpLogBslService.buildMrpLog(3, "每日销售订单计划排程执行结束!");
            System.out.println("每日销售订单计划排程执行结束!");
        } catch (Exception ex) {
            this.mrpLogBslService.buildMrpLog(2, "销售计划订单计划排程执行失败!");
            System.out.println("销售计划订单计划排程执行失败!");
            this.deletePlanOrder();
            ex.printStackTrace();
        } finally {
            return processMrpLog();
        }
    }

    /**
     * 更新是否已生成计划
     *
     * @return true、false;
     */
    private List<JSONObject> updateSaleOrders() {
        List<SaleOrder> saleOrders = this.saleOrderBslService.getSaleOrders();
        List<Integer> ids = saleOrders.stream().map(d -> d.getId()).collect(toList());
        List<String> idList = JSONUtil.parseArray(ids).toList(String.class);
        List<JSONObject> jsonObjectList = new ArrayList<>();
        for (String id : idList) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("id", id);
            jsonObject.put("className", "saleOrder");
            JSONObject values = new JSONObject();
            values.put("mrpId", this.mrpLogBslService.getMrpId());
            jsonObject.put("values", values);
            jsonObjectList.add(jsonObject);
        }
        List<JSONObject> resultObj = this.logicInstanceDslService.updateByIds(jsonObjectList);
        this.mrpLogBslService.buildMrpLog(1, "销售订单回写mrpId完成!");
        return resultObj;
    }

    /**
     * 计算下个月的月内示，生成委外和采购计划
     *
     * @param dataObj 参数
     * @return 返回结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultInfo<?> execMonthPlanMRP(JSONObject dataObj) {
        productPlanSummaryList = new ArrayList<>();
        int mrpId = dataObj.getInteger("mrpId");
        this.mrpLogBslService.initMrpLog(mrpId);
        this.sysConfigBslService.initSysConfig();
        this.materialBslService.initMaterial();
        this.productBomBslService.initBOM();
        this.stockDetailBslService.initMrpStock();
        this.workProcessPathBslService.initWorkProcessStat();
        //初始化MRP的基础数据
        this.monthPlanBslService.initMonthPlanMRP(dataObj);
        this.workCalendarBslService.initHoliday();
        try {
            this.deletePlanOrder();
            //初始化月内示设备占用
            this.equipmentOccupyBslService.initEquipmentOccupyByMonthPlan(dataObj);
            //计划类型
            this.planType = "内示";

            this.mrpLogBslService.buildMrpLog(1, "内示计划订单计划排程开始执行!");

            if (CollectionUtil.isNotEmpty(this.monthPlanBslService.getSaleOrderSummaryList())) {
                //处理销售订单计划与排程
                this.dealMonthPlanMRP(dataObj);
            } else {
                this.mrpLogBslService.buildMrpLog(2, "没有查到内示订单计划排程执行结束!");
            }

            this.mrpLogBslService.buildMrpLog(3, "内示计划订单计划排程执行结束!");

        } catch (Exception ex) {
            this.mrpLogBslService.buildMrpLog(2, "内示计划订单计划排程执行失败!");
            ex.printStackTrace();
        } finally {
            return processMrpLog();
        }
    }


    /**
     * 合并计划订单为生产计划
     *
     * @param dataObj 参数
     * @return 返回执行成功或失败
     */
    @Override
    public ResultInfo<?> execPlanOrderCombined(JSONObject dataObj) {
        this.asyncExecPlanOrderCombined(dataObj);
        this.updatePlanOrderCreateFlag(dataObj);
        return JsonResult.success(StatusCode.SUCCESS);
//        Thread thread = new Thread(new Runnable() {
//            private JSONObject dataObj;
//            // 构造方法传递参数
//            public Runnable init(JSONObject dataObj) {
//                this.dataObj = dataObj;
//                return this;
//            }
//            @Override
//            public void run() {
//                asyncExecPlanOrderCombined(dataObj);
//            }
//        }.init(dataObj)); // 通过调用init方法传递参数
//        // 启动线程
//        thread.start();
//        this.updatePlanOrderCreateFlag(dataObj);
//        return JsonResult.success(StatusCode.SUCCESS.getCode(),"合并计划订单由后台计算处理中，稍后再查看执行结果！");
    }

    @Transactional(rollbackFor = Exception.class)
    public void asyncExecPlanOrderCombined(JSONObject dataObj) {
        this.materialBslService.initMaterial();
        this.productBomBslService.initBOM();
        this.workProcessPathBslService.initWorkProcessStat();
        String planType = dataObj.getString("planType");
        JSONArray ids = dataObj.getJSONArray("ids");
        List<String> idList = JSONUtil.parseArray(ids).toList(String.class);
        JSONObject param = dataObj.getJSONObject("param");
        Integer period = param.getInteger("period");
        switch (planType) {
            case "purchasing":
                this.combinedPurchasePlanOrder(idList, period);
//                this.updatePlanOrderIsCreate("PurchasePlanOrder", idList);
                break;
            case "produce":
//                this.combinedProductionPlanOrderToWorkOrder(idList, period);
                this.combinedProductionPlanOrderToWorkOrder2(idList, period);
//                this.updatePlanOrderIsCreate("ProductionPlanOrder", idList);
                break;
            case "outsourcing":
                this.combinedOutsourcingPlanOrder(idList, period);
//                this.updatePlanOrderIsCreate("OutsourcingPlanOrder", idList);
                break;
            default:
                break;
        }
    }

    private void updatePlanOrderCreateFlag(JSONObject dataObj) {
        String planType = dataObj.getString("planType");
        JSONArray ids = dataObj.getJSONArray("ids");
        List<String> idList = JSONUtil.parseArray(ids).toList(String.class);
        switch (planType) {
            case "purchasing":
                this.updatePlanOrderIsCreate("PurchasePlanOrder", idList);
                break;
            case "produce":
                this.updatePlanOrderIsCreate("ProductionPlanOrder", idList);
                break;
            case "outsourcing":
                this.updatePlanOrderIsCreate("OutsourcingPlanOrder", idList);
                break;
            default:
                break;
        }
    }

    /**
     * 执行委外计划转订单
     *
     * @param actionData 参数，其中model 模型
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultInfo<?> execOutsourcingPlanTransformOrder(ActionData<JSONObject> actionData) {
        String model = actionData.getModel();
        Long containerId = actionData.getInstanceId();
        String groupByKey = actionData.getGroupKey();
        String parentNodeId = actionData.getParentId();
        // 查询主表记录
        JSONObject logicInstanceJson = logicInstanceDslService.queryJsonById(containerId);
        String className = logicInstanceJson.getString("className");
        List<HierInstance> hierInstances = hierInstanceDslService.queryChildByContainerId(model, className, containerId, parentNodeId, null);
        HierInstance hierIns = hierInstances.stream().filter(hIns -> StringUtils.isBlank(hIns.getValues().getString("supplier_id"))).findFirst().orElse(null);
        if (hierIns != null) {
            throw new BusinessException("999999", "执行委外计划转订单存在供应商没有维护，无法按供应商分组，请检查！");
        }
        Map<Object, List<HierInstance>> resultMap = hierInstanceDslService.fetchByParentNodeIdAndGroupBy(model, containerId, parentNodeId, groupByKey);
        // 根据上面的查询可以得出，需要分解成3条新记录，如何创建新的实例呢？ 新的主表记录的model, className, 字段映射关系
        List<JSONObject> resultJsonList = new ArrayList<>();
        resultMap.forEach((k, v) -> {
            JSONObject resultJson = new JSONObject();
            JSONObject tempInstanceJson = ObjectUtil.cloneByStream(logicInstanceJson);
            resultJson.put("master", tempInstanceJson);
            BigDecimal totalAmount = BigDecimal.ZERO;
            for (int i = 0; i < v.size(); i++) {
                HierInstance hierInstance = v.get(i);
                JSONObject childValues = hierInstance.getValues();
                String supplierCode = childValues.getString("supplier_code");
                String materialBcode = childValues.getString("material_bcode");
                BigDecimal taxRate = childValues.getBigDecimal("taxRate");
                BigDecimal price = childValues.getBigDecimal("price");
                JSONObject supplierPriceValues = this.purchaseBslServiceImpl.querySupplierPrice(supplierCode, materialBcode);
                if (price == null) {
                    price = supplierPriceValues == null ? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("price");
                }
                BigDecimal processingFee = childValues.getBigDecimal("processingFee");
                if (processingFee == null) {
                    processingFee = supplierPriceValues == null ? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("processingFee");
                }
                if (taxRate == null) {
                    taxRate = supplierPriceValues == null ? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("taxRate");
                    taxRate = taxRate != null ? taxRate :  BigDecimal.ZERO;
                }
                childValues.put("taxRate", taxRate);
                childValues.put("price", price);
                childValues.put("processingFee", processingFee);
                BigDecimal quantity = childValues.getBigDecimal("quantity");
                BigDecimal tempPrice = price.add(processingFee);
                BigDecimal amount = quantity.multiply(tempPrice);
                childValues.put("amount", amount);
                totalAmount = totalAmount.add(amount);
            }
            resultJson.put("slave", v);
            tempInstanceJson.getJSONObject("values").put("totalAmount", totalAmount);
            resultJsonList.add(resultJson);
        });
        JSONObject dataJson = actionData.getData().get(0);
        JSONObject targetJson = dataJson.getJSONObject("target");
        List propertyMapList = dataJson.getJSONArray("propertyMapList");
        propertyMapList.add("master.values.totalAmount:master.totalAmount");
        logicInstanceBslService.copy1(resultJsonList, targetJson, propertyMapList);
        this.dealOutsourcingOrderQuantity(containerId);
        return JsonResult.success(resultJsonList);
    }

    /**
     * 委外发料表
     *
     * @param actionData
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultInfo<?> execOutsourcingOrderSendData(ActionData<JSONObject> actionData) {
        String model = actionData.getModel();
        Long containerId = actionData.getInstanceId();
        String parentNodeId = actionData.getParentId();
        // 查询主表记录
        JSONObject logicInstanceJson = logicInstanceDslService.queryJsonById(containerId);
        JSONObject values = logicInstanceJson.getJSONObject("values");
        String supplierCode = values.getString("supplier_code");

        List<HierInstance> hierInstances = hierInstanceDslService.fetchByParentNodeId(model, containerId, parentNodeId);
        final BigDecimal[] totalAmount = {BigDecimal.ZERO};
        hierInstances.forEach(hIns -> {
            JSONObject childValues = hIns.getValues();
            String materialBcode = childValues.getString("material_bcode");
            BigDecimal price = childValues.getBigDecimal("price");
            JSONObject supplierPriceValues = this.purchaseBslServiceImpl.querySupplierPrice(supplierCode, materialBcode);
            if (price == null) {
                price = supplierPriceValues == null ? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("price");
            }
            BigDecimal processingFee = childValues.getBigDecimal("processingFee");
            if (processingFee == null) {
                processingFee = supplierPriceValues == null ? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("processingFee");
            }
            BigDecimal priceNonTax = childValues.getBigDecimal("priceNonTax");
            if (priceNonTax == null) {
                priceNonTax = supplierPriceValues == null? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("priceNonTax");
                priceNonTax = priceNonTax != null ? priceNonTax : BigDecimal.ZERO;
            }
            BigDecimal taxRate = childValues.getBigDecimal("taxRate");
            if (taxRate == null) {
                taxRate = supplierPriceValues == null ? BigDecimal.ZERO : supplierPriceValues.getBigDecimal("taxRate");
                taxRate = taxRate != null ? taxRate :  BigDecimal.ZERO;
            }

            childValues.put("price", price);
            childValues.put("taxRate", taxRate);
            childValues.put("priceNonTax", priceNonTax);
            childValues.put("processingFee", processingFee);
            BigDecimal quantity = childValues.getBigDecimal("quantity");
            quantity = quantity == null ? BigDecimal.ZERO : quantity;
            BigDecimal tempPrice = price.add(processingFee);

            BigDecimal amount = quantity.multiply(tempPrice);
            BigDecimal money = quantity.multiply(price);
            BigDecimal moneyNonTax = quantity.multiply(priceNonTax);
            BigDecimal processingFeeMoney = quantity.multiply(processingFee);

            childValues.put("money", money);
            childValues.put("moneyNonTax", moneyNonTax);
            childValues.put("processingFeeMoney", processingFeeMoney);
            childValues.put("amount", amount);

            totalAmount[0] = totalAmount[0].add(amount);
            hierInstanceDslService.updateById(hIns);
        });

        logicInstanceJson.getJSONObject("values").put("totalAmount", totalAmount[0]);
        logicInstanceDslService.updateById(logicInstanceJson);
        return JsonResult.success(logicInstanceJson);
    }


    /**
     * 委外订单明细数据用量
     *
     * @param dataObj 参数
     * @return 返回结果
     */
    @Override
    public ResultInfo<?> execOutsourcingOrderQuantity(JSONObject dataObj) {
        this.materialBslService.initMaterial();
        this.productBomBslService.initBOM();
        JSONArray ids = dataObj.getJSONArray("ids");
        List<String> idList = JSONUtil.parseArray(ids).toList(String.class);
        idList.forEach(id -> {
            this.dealOutsourcingOrderSubMaterial(Long.valueOf(id));
        });
        return JsonResult.success(StatusCode.SUCCESS);
    }

    /**
     * 工单齐套分析
     *
     * @return 返回分析结果
     */
    @Override
    public ResultInfo<?> execCompleteAnalysis(JSONObject dataObj) {
        Long id = dataObj.getLong("id");
        JSONObject workOrderJson = this.logicInstanceDslService.queryJsonById(id);
        JSONObject workOrderValues = workOrderJson.getJSONObject("values");
        List<HierInstance> hierInstances = this.hierInstanceDslService.queryChildByContainerId(ModelType.Logic.getCode(), "workOrder", id, "458qninw7j8000", null);
        List<HierInstance> materialHierInstances = hierInstances.stream().filter(hIns -> "1".equalsIgnoreCase(hIns.getValues().getString("subType"))).collect(toList());
        String status = workOrderValues.getString("status");
        if (!"init".equalsIgnoreCase(status)) {
            JSONObject completeAnalysisData = this.getCompleteAnalysisData(workOrderValues, materialHierInstances);
            return JsonResult.success(completeAnalysisData);
        }

        String mIdList = materialHierInstances.stream().map(d -> d.getValues().getString("material_id")).collect(Collectors.joining(","));
        String mCondition = " id in ( " + mIdList + ")";
        this.materialBslService.initMaterialByCondition(mCondition);

        String wCondition = " material_id in ( " + mIdList + ")";
        this.stockDetailBslService.initCompleteAnalysisStock(wCondition);
        this.stockDetailBslService.setStockMap(this.stockDetailBslService.getInitStockMap());

        List<JSONObject> dataList = new ArrayList<>();
        AtomicBoolean completeAnalysis = new AtomicBoolean(true);
        materialHierInstances.forEach(hIns -> {
            JSONObject values = hIns.getValues();
            values.put("id", hIns.getId());
            String materialBCode = values.getString("material_bcode");
            Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);

            Double stockQuantity = this.stockDetailBslService.getStocks().stream().filter(s -> s.getMaterialBCode().equalsIgnoreCase(materialBCode)).mapToDouble(Stock::getQuantity).sum();
            if (stockQuantity == null) {
                stockQuantity = 0.0;
            }

            Double stockSubQuantity = this.stockDetailBslService.getStocks().stream().filter(s -> s.getMaterialBCode().equalsIgnoreCase(materialBCode)).mapToDouble(Stock::getSubQuantity).sum();
            if (stockSubQuantity == null) {
                stockSubQuantity = 0.0;
            }
            Integer quantity = values.getInteger("quantity");
            Double conversionRate = material.getConversionRate();
            double subQuantity;
            if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
                subQuantity = 1;
                conversionRate = quantity * 1.0;
            } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
                subQuantity = TypeUtils.ceilNumber(quantity * 1.0 / conversionRate);
            } else {
                throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常！", material.getBcode()));
            }
            values.put("stockQuantity", stockQuantity);
            values.put("stockSubQuantity", stockSubQuantity);
            Double diffValue = stockQuantity - quantity;
            values.put("diffValue", diffValue);
            values.put("material_id", material.getId());
            //领：1，或其它，派：2
            values.put("dispatchFlag", material.getDispatchFlag());
            values.put("unit", material.getUnit());
            values.put("subUnit", material.getSubUnit());
            values.put("subQuantity", subQuantity);
            values.put("conversionRate", conversionRate);
            values.put("material_useMultiple", material.getUseMultiple());
            if (material.getWarehouseId() != null) {
                JSONObject warehouseJson = this.hierInstanceDslService.queryById(material.getWarehouseId());
                JSONObject whValues = warehouseJson.getJSONObject("values");
                values.put("sourceWarehouseId", warehouseJson.getLong("id"));
                values.put("sourceWarehouseCode", whValues.getString("code"));
                values.put("sourceWarehouseName", whValues.getString("name"));
            }
            if (diffValue >= 0) {
                values.put("mark", true);
            } else {
                values.put("mark", false);
                completeAnalysis.set(false);
            }
            dataList.add(values);
        });
        JSONObject resultObj = this.buildCompleteAnalysis(id, dataList, completeAnalysis.get());
        return JsonResult.success(resultObj);
    }

    private JSONObject buildCompleteAnalysis(Long id, List<JSONObject> dataList, Boolean flag) {
        JSONObject jsonObject = this.logicInstanceDslService.queryJsonById(id);
        JSONObject orderValues = jsonObject.getJSONObject("values");
        String productBCode = orderValues.getString("material_bcode");
        String bomType = orderValues.getString("bomType");
        if (bomType.equalsIgnoreCase("combined")) {
            this.productBomBslService.initCombinedBom(productBCode);
        } else {
            this.productBomBslService.initSplitBom(productBCode);
        }

        List<ProductBomSub> productBomSubList = new ArrayList<>();
        ProductBomMaster productBomMaster = this.productBomBslService.getProductBomList().stream().filter(d -> d.getMaterialBCode().equalsIgnoreCase(productBCode)).findFirst().orElse(null);
        if (productBomMaster != null) {
            productBomSubList.addAll(productBomMaster.getMaterialBomSubList());
        }
        dataList.forEach(d -> {
            String materialBCode = d.getString("material_bcode");
            ProductBomSub productBomSub = productBomSubList.stream().filter(pb -> pb.getMaterialBCode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
            if (productBomSub == null) {
                throw new BusinessException("999999", String.format("背番：%s物料BOM没有维护,齐套分析失败！", materialBCode));
            }

            Integer invertWarehouse = productBomSub.getInvertWarehouse();
            if (invertWarehouse == null) {
                throw new BusinessException("999999", String.format("背番：%s物料BOM的倒冲仓库没有维护,齐套分析失败！", materialBCode));
            }

            JSONObject warehouseJson = this.hierInstanceDslService.queryById(Long.valueOf(invertWarehouse));
            JSONObject whValues = warehouseJson.getJSONObject("values");
            d.put("warehouse_id", invertWarehouse);
            d.put("warehouse_code", whValues.getString("code"));
            d.put("warehouse_name", whValues.getString("name"));
            Integer invertLocation = productBomSub.getInvertLocation();
            JSONObject binJson = this.hierInstanceDslService.queryById(Long.valueOf(invertLocation));
            JSONObject binValues = binJson.getJSONObject("values");
            d.put("bin_id", invertLocation);
            d.put("bin_code", binValues.getString("code"));
            d.put("bin_name", binValues.getString("name"));
            Integer quantity = d.getInteger("quantity");
            List<Stock> stocks = this.stockDetailBslService.getStocks();
            Double deductQuantity = stocks.stream().filter(st ->
                    st.getWarehouseId().compareTo(invertWarehouse) == 0 && invertLocation != null && st.getBinId().compareTo(invertLocation) == 0 &&
                            st.getMaterialBCode().equalsIgnoreCase(materialBCode)
            ).collect(summingDouble(Stock::getQuantity));
            if (deductQuantity == null || TypeUtils.ceilNumber(deductQuantity * 100) == 0) {
                deductQuantity = 0.0;
            }
            // 如果需求数量小于等于线边库存数量，则不领料
            if (quantity.compareTo(deductQuantity.intValue()) <= 0) {
                d.put("pickFlag", false); //false:不领料标识
//                d.put("pickQuantity", 0);
            } else {
                d.put("pickFlag", true); //true:领料标识
//                d.put("pickQuantity", quantity -deductQuantity);
            }

            d.put("pickQuantity", 0.0);

            Double deductSubQuantity = stocks.stream().filter(st ->
                    st.getWarehouseId().compareTo(invertWarehouse) == 0 && invertLocation != null && st.getBinId().compareTo(invertLocation) == 0 &&
                            st.getMaterialBCode().equalsIgnoreCase(materialBCode)
            ).collect(summingDouble(Stock::getSubQuantity));
            if (deductSubQuantity == null || TypeUtils.ceilNumber(deductSubQuantity * 100) == 0) {
                deductSubQuantity = 0.0;
            }
            d.put("deductQuantity", deductQuantity);
            d.put("deductSubQuantity", deductSubQuantity);
        });

        JSONObject resultObj = new JSONObject();
        resultObj.put("completeAnalysis", flag);
        resultObj.put("data", dataList);
        resultObj.put("productBCode", productBCode);
        return resultObj;
    }


    /**
     * 获取已经下派的齐友分析数据
     *
     * @param workOrderValues
     * @param hierInstances
     */
    private JSONObject getCompleteAnalysisData(JSONObject workOrderValues, List<HierInstance> hierInstances) {
        JSONObject resultObj = new JSONObject();
        List<JSONObject> dataList = new ArrayList<>();
        Boolean completeAnalysis = workOrderValues.getBoolean("completeAnalysis");
        String productBCode = workOrderValues.getString("material_bcode");
        for (HierInstance hIns : hierInstances) {
            JSONObject values = hIns.getValues();
            dataList.add(values);
        }
        resultObj.put("completeAnalysis", completeAnalysis ? true : false);
        resultObj.put("data", dataList);
        resultObj.put("productBCode", productBCode);
        return resultObj;
    }

    private void dealOutsourcingOrderQuantity(Long containerId) {
        this.materialBslService.initMaterial();
        this.productBomBslService.initBOM();
        List<LogicInstance> outsourcingOrder = logicInstanceDslService.fetchInstanceList(" planId = " + containerId, "OutsourcingOrder", null);
        List<Long> ids = outsourcingOrder.stream().map(d -> d.getId()).collect(toList());
        ids.forEach(id -> {
            this.dealOutsourcingOrderSubMaterial(id);
        });
    }

    private void dealOutsourcingOrderSubMaterial(Long id) {
        List<OutsourcingProduct> products = this.mrpBslService.fetchOutsourcingProduct(id);
        Map<String, Double> productMap = products.stream().collect(groupingBy(OutsourcingProduct::getMaterialBCode, summingDouble(OutsourcingProduct::getQuantity)));
        List<OutsourcingSubMaterial> outsourcingSubMaterials = new ArrayList<>();
        for (Map.Entry<String, Double> tempEntry : productMap.entrySet()) {
            String key = tempEntry.getKey();
            Double value = tempEntry.getValue();
            List<OutsourcingSubMaterial> tempSubMaterials = this.mrpBslService.fetchOutsourcingSubMaterial(key, value);
            outsourcingSubMaterials.addAll(tempSubMaterials);
        }

        Map<String, Double> subMaterialMap = outsourcingSubMaterials.stream().collect(groupingBy(OutsourcingSubMaterial::getMaterialBCode, summingDouble(OutsourcingSubMaterial::getQuantity)));
        this.hierInstanceDslService.deleteByParentId(ModelType.Logic.getCode(), id, "o5lcmc1zl2o00");
        for (Map.Entry<String, Double> tempEntry : subMaterialMap.entrySet()) {
            String materialBCode = tempEntry.getKey();
            Double quantity = tempEntry.getValue();
            Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
            Double conversionRate = material.getConversionRate();
            double subQuantity;
            if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
                subQuantity = 1;
                conversionRate = quantity * 1.0;
            } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
                subQuantity = TypeUtils.ceilNumber(quantity * 1.0 / conversionRate);
            } else {
                throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常！", material.getBcode()));
            }
            JSONObject values = new JSONObject();
            values.put("material_id", material.getId());
            values.put("material_pcode", material.getPcode());
            values.put("material_bcode", materialBCode);
            values.put("material_spec", material.getSpec());
            values.put("material_model", material.getModel());
            values.put("material_unit", material.getUnit());
            values.put("material_subUnit", material.getSubUnit());
            values.put("quantity", quantity);
            values.put("subQuantity", subQuantity);
            values.put("conversionRate", conversionRate);
            values.put("price", material.getReferToPrice());

            JSONObject hierInstanceJson = new JSONObject();
            hierInstanceJson.put("parentId", "o5lcmc1zl2o00");
            hierInstanceJson.put("containerId", id);
            hierInstanceJson.put("containerModel", ModelType.Logic.getCode());
            hierInstanceJson.put("className", "OutsourcingOrder");
            hierInstanceJson.put("values", values);
            this.hierInstanceDslService.addChild(hierInstanceJson);
        }
    }

    /**
     * 更新是否已生成计划
     *
     * @return true、false;
     */
    private List<JSONObject> updatePlanOrderIsCreate(String className, List<String> idList) {
        List<JSONObject> jsonObjectList = new ArrayList<>();
        for (String id : idList) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("id", id);
            jsonObject.put("className", className);
            JSONObject values = new JSONObject();
            values.put("isCreate", true);
            jsonObject.put("values", values);
            jsonObjectList.add(jsonObject);
        }
        List<JSONObject> resultObj = this.logicInstanceDslService.updateByIds(jsonObjectList);
        return resultObj;
    }

    /**
     * 处理执行结果日志
     *
     * @return 成功或失败信息
     */
    private ResultInfo<?> processMrpLog() {
        List<MrpLog> mrpLogs = this.mrpLogBslService.getMrpLogs();
        MrpLog mrpLog = mrpLogs.stream().filter(m -> m.getStatus() == 2).findFirst().orElse(null);
        this.mrpLogBslService.batchUpdateMrpLog();
        if (mrpLog != null) {
            return JsonResult.error(999999, "计划与排程失败！");
        } else {
            return JsonResult.success(StatusCode.SUCCESS);
        }
    }

    /**
     * 处理销售订单计划MRP
     */
    private void dealSalePlanMRP() {
        this.productPlanSummaryList = new ArrayList<>();
        Map<String, Double> targetMap = this.stockDetailBslService.getInitStockMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        LocalDate deliveryStartDate = this.effectiveTime;
        //销售订单按天汇总列表
        List<SaleOrderSummary> saleOrderSummaryList = this.saleOrderBslService.getSaleOrderSummaryList();
        //构造销售订单按天汇总以发货日期为key,销售列表为value的Map
        Map<LocalDate, List<SaleOrderSummary>> saleOrderSummaryMap = saleOrderSummaryList.stream()
                .sorted(Comparator.comparing(SaleOrderSummary::getDeliveryDate).reversed()).collect(groupingBy(SaleOrderSummary::getDeliveryDate));

        //将Map进行按key 排序
        Map<LocalDate, List<SaleOrderSummary>> sortSaleOrderSummaryMap = saleOrderSummaryMap.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(
                        LinkedHashMap::new,
                        (result, entry) -> result.put(entry.getKey(), entry.getValue()),
                        LinkedHashMap::putAll
                );

        this.stockDetailBslService.setStockMap(targetMap);
        List<ProductBomSummary> planProductBomSummaryList = new ArrayList<>();
        for (Map.Entry<LocalDate, List<SaleOrderSummary>> entry : sortSaleOrderSummaryMap.entrySet()) {
            LocalDate deliveryEndDate = entry.getKey();
            List<SaleOrderSummary> tempSaleOrderSummaryList = entry.getValue();
            this.calculateExecutionStock(deliveryStartDate, deliveryEndDate);

            List<ProductBomLevel> productBomLevelList = new ArrayList<>();
            System.out.println("=================================" + entry);
            for (SaleOrderSummary saleOrderSummary : tempSaleOrderSummaryList) {
                System.out.println("***********************************" + saleOrderSummary + "******************start");
                this.saleOrderBslService.setCurrentSaleOrderSummary(saleOrderSummary);
                //构建物料计划层级列表(productBomLevels)
                this.productBomBslService.buildProductBomLevel(saleOrderSummary.getMaterialBCode());
                List<ProductBomLevel> productBomLevels = this.productBomBslService.getProductBomLevels();
                this.buildSalePlanBomLevels(productBomLevels);
                //过虑掉计划量小于等于0的数据
                List<ProductBomLevel> tempProductBomLevelList = productBomLevels.stream().filter(pb -> pb.getPlanQuantity() > 0).collect(toList());
                productBomLevelList.addAll(tempProductBomLevelList);
                System.out.println("***********************************" + saleOrderSummary + "******************end");
            }

            //按天汇总生产、采购、委外计划量
            List<ProductBomSummary> productBomSummaryList = productBomLevelList.stream()
                    .collect(Collectors.groupingBy(
                            product -> product.getMaterialBCode(),
                            Collectors.summingDouble(ProductBomLevel::getPlanQuantity)
                    ))
                    .entrySet()
                    .stream()
                    .map(entryObj -> getProductBomSummary(entryObj, deliveryEndDate))
                    .filter(p -> p.getPlanQuantity() > 0)
                    .sorted(Comparator.comparing(ProductBomSummary::getPlanEndDate))
                    .collect(Collectors.toList());

            planProductBomSummaryList.addAll(productBomSummaryList);
            deliveryStartDate = deliveryEndDate.plusDays(1);
        }

        this.buildProductBomPlan(planProductBomSummaryList);
    }

    /**
     * 计算优先级
     *
     * @param saleOrderSummaryList 生产列表
     * @return 优先级顺序列表
     */
    private List<SaleOrderSummary> dealSaleOrderSummaryPriority(List<SaleOrderSummary> saleOrderSummaryList) {
        List<SaleOrderSummary> tempSaleOrderSummaryList = new ArrayList<>();
        List<ProductBomLevel> productBomLevelList = new ArrayList<>();
        for (int i = 0; i < saleOrderSummaryList.size(); i++) {
            SaleOrderSummary saleOrderSummary = saleOrderSummaryList.get(i);
            String materialBCode = saleOrderSummary.getMaterialBCode();
            ProductBomLevel productBomLevel = productBomLevelList.stream().filter(p -> p.getMaterialBCode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
            this.productBomBslService.buildProductBomLevel(saleOrderSummary.getMaterialBCode());
            List<ProductBomLevel> productBomLevels = this.productBomBslService.getProductBomLevels();
            productBomLevelList.addAll(productBomLevels);
            if (productBomLevel == null) {
                tempSaleOrderSummaryList.add(saleOrderSummary);
            } else {
                tempSaleOrderSummaryList.add(0, saleOrderSummary);
            }
        }
        return tempSaleOrderSummaryList;
    }


    private void dealMonthPlanMRP(JSONObject dataObj) {
        JSONObject param = dataObj.getJSONObject("param");
        String yearMonth = param.getString("yearMonth");
        String[] ymAry = yearMonth.split("-");
        int year = Integer.valueOf(ymAry[0]);
        int month = Integer.valueOf(ymAry[1]);
        LocalDate firstDate = LocalDate.of(year, month, 1);
        LocalDate today = LocalDate.now();
        LocalDate startLocalDate;
        LocalDate endLocalDate;
        if (firstDate.getYear() == today.getYear() && firstDate.getMonth() == today.getMonth()) {
            startLocalDate = today;
        } else {
            startLocalDate = firstDate;
        }
        endLocalDate = firstDate.with(TemporalAdjusters.lastDayOfMonth());
        List<Holiday> holidays = this.workCalendarBslService.getHolidayList(startLocalDate, endLocalDate);
        LocalDate deliveryStartDate = this.effectiveTime;
        Map<String, Double> targetMap = this.stockDetailBslService.getInitStockMap().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        LocalDate localDateEnd = endLocalDate.plusDays(1);
        this.stockDetailBslService.setStockMap(targetMap);
        List<ProductBomSummary> planProductBomSummaryList = new ArrayList<>();
        while (startLocalDate.isBefore(localDateEnd)) {
            Boolean isHoliday = this.workCalendarBslService.checkHoliday(startLocalDate);
            if (isHoliday) {
                startLocalDate = startLocalDate.plusDays(1);
                continue;
            }
            List<ProductBomLevel> productBomLevelList = new ArrayList<>();
            this.calculateExecutionStock(deliveryStartDate, startLocalDate);
            System.out.println(this.stockDetailBslService.getStockMap());
            List<SaleOrderSummary> saleOrderSummaryList = this.monthPlanBslService.getSaleOrderSummaryList();
            for (SaleOrderSummary saleOrderSummary : saleOrderSummaryList) {
                saleOrderSummary.setDeliveryDate(startLocalDate);
                this.saleOrderBslService.setCurrentSaleOrderSummary(saleOrderSummary);
                //构建物料计划层级列表(productBomLevels)
                this.productBomBslService.buildProductBomLevel(saleOrderSummary.getMaterialBCode());
                List<ProductBomLevel> productBomLevels = this.productBomBslService.getProductBomLevels();
                this.buildSalePlanBomLevels(productBomLevels);
                //过虑计划量小于0的数据
                List<ProductBomLevel> tempProductBomLevelList = productBomLevels.stream().filter(pb -> pb.getPlanQuantity() > 0).collect(toList());
                productBomLevelList.addAll(tempProductBomLevelList);
            }

            //按天汇总生产、采购、委外计划量
            LocalDate finalStartLocalDate = startLocalDate;
            List<ProductBomSummary> productBomSummaryList = productBomLevelList.stream()
                    .collect(Collectors.groupingBy(
                            product -> product.getMaterialBCode(),
                            Collectors.summingDouble(ProductBomLevel::getPlanQuantity)
                    ))
                    .entrySet()
                    .stream()
                    .map(entryObj -> getProductBomSummary(entryObj, finalStartLocalDate))
                    .filter(p -> p.getPlanQuantity() > 0)
                    .sorted(Comparator.comparing(ProductBomSummary::getPlanEndDate))
                    .collect(Collectors.toList());

            planProductBomSummaryList.addAll(productBomSummaryList);
            System.out.println(this.stockDetailBslService.getStockMap());
            startLocalDate = startLocalDate.plusDays(1);
            deliveryStartDate = startLocalDate;
        }

        this.buildProductBomPlan(planProductBomSummaryList);
    }

    /**
     * 按天汇总生产、采购、委外计划量
     *
     * @param entry
     * @param deliveryDate
     * @return
     */
    public ProductBomSummary getProductBomSummary(Map.Entry<String, Double> entry, LocalDate deliveryDate) {
        ProductBomSummary productBomSummary = new ProductBomSummary();
        String materialBCode = entry.getKey();
        double totalPlanQuantity = entry.getValue();
        productBomSummary.setDeliveryDate(deliveryDate);
        productBomSummary.setPlanEndDate(deliveryDate);
        productBomSummary.setMaterialBCode(materialBCode);
        Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        productBomSummary.setMaterialAttr(material.getAttr());
        Integer minBatch = material.getMinBatch();
        if (minBatch == null) {
            minBatch = 1;
        }
        SaleOrderSub saleOrderSub = this.saleOrderBslService.getTrialOrderSubs().stream().filter(o -> o.getMaterialBCode().equalsIgnoreCase(materialBCode) && o.getDeliveryDate().isEqual(deliveryDate) && o.isTrial()).findFirst().orElse(null);
        if (saleOrderSub != null) {
            productBomSummary.setEquipmentList(saleOrderSub.getTrialEquipments());
            productBomSummary.setPlanQuantity(totalPlanQuantity);
            productBomSummary.setProduceLeadTime(0.0);
            productBomSummary.setTrial(true);
            Integer trialDuration = saleOrderSub.getTrialDuration();
            Double trialAvgDuration = trialDuration * 1.0 / totalPlanQuantity;
            List<Long> trialEquipments = saleOrderSub.getTrialEquipments();
            productBomSummary.setEquipmentList(trialEquipments);
            productBomSummary.setProduceAvgDuration(trialAvgDuration);
            productBomSummary.setProduceDuration(saleOrderSub.getTrialDuration());
        } else {
            switch (material.getAttr()) {
                case "purchasing":
                    //采购周期按天算
                    if (material.getPurchasingPeriod() == null) {
                        String msg = String.format("背番: %s物料采购周期为空，计划排程失败！", materialBCode);
                        this.mrpLogBslService.buildMrpLog(2, msg);
                        throw new BusinessException("999999", msg);
                    } else {
                        productBomSummary.setPurchasingPeriod(material.getPurchasingPeriod());
                        productBomSummary.setPlanQuantity(totalPlanQuantity);
                    }
                    break;
                case "produce":
                    //生产提前期（换模等准备工作）小时
                    double produceLeadTime = material.getProduceLeadTime() == null ? 0.0 : material.getProduceLeadTime();
                    productBomSummary.setProduceLeadTime(produceLeadTime);
                    //根据工艺路径的工序中的节拍计算耗时
                    WorkProcessPath workProcessPath = this.workProcessPathBslService.fetchWorkProcessPath(materialBCode);
                    if (workProcessPath == null) {
                        String msg = String.format("背番: %s物料工艺路径没有维护，计划与排程运行失败！", materialBCode);
                        this.mrpLogBslService.buildMrpLog(2, msg);
                        throw new BusinessException("999999", msg);
                    }

                    List<Long> equipmentList = workProcessPath.getEquipmentList();
                    if (CollectionUtil.isEmpty(equipmentList)) {
                        String msg = String.format("背番: %s物料工艺路径没有维护机台设备，计划与排程运行失败！", materialBCode);
                        this.mrpLogBslService.buildMrpLog(2, msg);
                        throw new BusinessException("999999", msg);
                    }

                    equipmentList = equipmentList.stream().distinct().collect(toList());
                    productBomSummary.setEquipmentList(equipmentList);
                    productBomSummary.setPlanQuantity(totalPlanQuantity);
                    //生产每件产品耗时
                    productBomSummary.setProduceAvgDuration(workProcessPath.getDuration());
                    //计算生产总耗时
                    double produceTotalDuration = workProcessPath.getDuration() * (totalPlanQuantity + 1);
                    produceTotalDuration = Math.round(produceTotalDuration * 10000) / 10000.0;
                    productBomSummary.setProduceDuration(produceTotalDuration);
                    //最小批量生产的最小耗时
                    double produceMinBatchDuration = workProcessPath.getDuration() * (minBatch + 1);
                    produceMinBatchDuration = Math.round(produceMinBatchDuration * 10000) / 10000.0;
                    productBomSummary.setProduceMinBatchDuration(produceMinBatchDuration);
                    break;
                case "outsourcing":
                    //采购周期按天算
                    if (material.getOutsourcingPeriod() == null) {
                        String msg = String.format("背番: %s物料委外周期为空，MRP运行失败！", materialBCode);
                        this.mrpLogBslService.buildMrpLog(2, msg);
                        throw new BusinessException("999999", msg);
                    } else {
                        productBomSummary.setOutsourcingPeriod(material.getOutsourcingPeriod());
                        productBomSummary.setPlanQuantity(totalPlanQuantity);
                    }
                    break;
                default:
                    break;
            }
        }

        return productBomSummary;
    }

    /**
     * 构建生产物料计划
     *
     * @param productBomSummaryList
     */
    private void buildProductBomPlan(List<ProductBomSummary> productBomSummaryList) {
        //生产物料按计划完成日期从早到晚排序
        Map<LocalDate, List<ProductBomSummary>> sorteProductSummaryMap = sortProductBomSummaryByPlanEndDate(productBomSummaryList);
        //循环处理每天生产、采购、委外计划汇总量
        for (Map.Entry<LocalDate, List<ProductBomSummary>> entry : sorteProductSummaryMap.entrySet()) {
            LocalDate planEndDate = entry.getKey();
            //生产列表
            List<ProductBomSummary> produceList = entry.getValue().stream().filter(p -> p.getMaterialAttr().equalsIgnoreCase("produce")).collect(toList());
            //采购列表
            List<ProductBomSummary> purchasingList = entry.getValue().stream().filter(p -> p.getMaterialAttr().equalsIgnoreCase("purchasing")).collect(toList());
            //委外列表
            List<ProductBomSummary> outsourcingList = entry.getValue().stream().filter(p -> p.getMaterialAttr().equalsIgnoreCase("outsourcing")).collect(toList());
            List<Map<String, List<Long>>> listMap = exclusionGroupProductBomSummary(produceList);
            for (Map<String, List<Long>> map : listMap) {
                for (Map.Entry<String, List<Long>> mapEntry : map.entrySet()) {
                    String materialBCode = mapEntry.getKey();
                    List<Long> equipmentList = mapEntry.getValue();
                    System.out.println("========================================================================");
                    System.out.println(String.format("成品背番：%s,使用设备ids:", materialBCode));
                    System.out.println(equipmentList);
                    System.out.println("========================================================================");
                    ProductBomSummary productBomSummary = produceList.stream().filter(p -> p.getMaterialBCode().equalsIgnoreCase(materialBCode)).findFirst().get();
                    //当前生产成品所需设备占用列表
                    List<EquipmentOccupy> equipmentOccupies = this.fetchEquipmentOccupyList(equipmentList, planEndDate);
                    //获取当前生产成品所需设备的最小空闲有效时长
                    double availableDuration = equipmentOccupies.stream().mapToDouble(EquipmentOccupy::getEffectiveDuration).min().getAsDouble();
                    //获取当前生产成品所需生产时长
                    double produceDuration = productBomSummary.getProduceDuration();
                    //生产一个产品平均耗时（小时） 根据节拍（每分钟生产个数）换算来的
                    double produceAvgDuration = productBomSummary.getProduceAvgDuration();
                    //最小批量生产最小耗时
                    double produceMinBatchDuration = productBomSummary.getProduceMinBatchDuration();
                    //生产成品数量
                    double planQuantity = productBomSummary.getPlanQuantity();
                    //当前生产成品所需提前期时长
                    double produceLeadTime = productBomSummary.getProduceLeadTime();
                    //已排过产品计划列表中有当前生产成品
                    ProductBomSummary hasProductBomSummary = productPlanSummaryList.stream().filter(p -> p.getMaterialBCode().equalsIgnoreCase(materialBCode) && p.getPlanEndDate().isEqual(planEndDate)).sorted(Comparator.comparing(ProductBomSummary::getEndTime).reversed()).findFirst().orElse(null);
                    //需求总生产时长
                    double totalProduceDuration;
                    if (hasProductBomSummary == null) {
                        totalProduceDuration = produceDuration + produceLeadTime;
                    } else {
                        totalProduceDuration = produceDuration;
                    }
                    //如果 availableDuration > produceDuration + produceLeadTime 为true,说明当天可以完成生产任务
                    //否则说明当前生产成品时长超出了当天有效工时,需要分配到其它日期完成，否则不需要分配到其它日期完成
                    if (totalProduceDuration > availableDuration) {
                        //计算超出的工时数（小时）
                        double diffProduceDuration = totalProduceDuration - availableDuration;
                        //计算超出的数量  超出数量 = 超出时长 / 每件时长  结果上取整
                        double diffPlanQuantity = TypeUtils.ceilNumber((diffProduceDuration / produceAvgDuration) * 100 / 100.0);
                        //当前天计划生产数量 = 计划数量-超出的数量
                        double currentPlanQuantity = planQuantity - diffPlanQuantity;
                        //构造当前计划日期排程数据对象
                        ProductBomSummary currentProductGroupSummary = new ProductBomSummary();
                        //对象复制
                        BeanUtils.copyProperties(productBomSummary, currentProductGroupSummary);
                        //当前计划日期排程数量
                        currentProductGroupSummary.setPlanQuantity(currentPlanQuantity);
                        //当前计划日期排程占用时长
                        currentProductGroupSummary.setProduceDuration(availableDuration);
                        //当前计划日期当前成品排程空闲时长，由于生产成品时长超出了当天有效工时，所以空闲工时为0
                        currentProductGroupSummary.setFreeDuration(0);
                        //计算当前计划的开始结果时间
                        calculateCurrentStartEndTime(hasProductBomSummary, currentProductGroupSummary, availableDuration);
                        //由于当前计划日期还没有排当前生产成品，可以直接排进计划列表中
                        productPlanSummaryList.add(currentProductGroupSummary);
                        //调整设备占用及空闲时长
                        this.adjustEquipmentOccupy(materialBCode, currentProductGroupSummary.getPlanEndDate(), equipmentList, availableDuration);
                        //处理超出一个工作日的成生成品计划
                        this.processingProductPlanSummary(productBomSummary, diffProduceDuration, diffPlanQuantity);
                    } else {
                        //空闲长时
                        double freeDuration = availableDuration - totalProduceDuration;
                        ProductBomSummary currentProductGroupSummary = new ProductBomSummary();
                        BeanUtils.copyProperties(productBomSummary, currentProductGroupSummary);
                        currentProductGroupSummary.setPlanQuantity(planQuantity);
                        currentProductGroupSummary.setProduceDuration(totalProduceDuration);
                        currentProductGroupSummary.setFreeDuration(freeDuration);
                        freeDuration = TypeUtils.floorNumber(freeDuration * 100) / 100.0;
                        totalProduceDuration = TypeUtils.ceilNumber(totalProduceDuration * 100) / 100.0;
                        calculateFinishCurrentStartEndTime(currentProductGroupSummary, freeDuration, totalProduceDuration);
                        productPlanSummaryList.add(currentProductGroupSummary);
                        //调整设备占用及空闲时长
                        this.adjustEquipmentOccupy(materialBCode, currentProductGroupSummary.getPlanEndDate(), equipmentList, totalProduceDuration);
                    }

                    this.productBomBslService.setProductBomLevels(new ArrayList<>());
                    this.productBomBslService.buildProductBomLevel(materialBCode);
                    this.buildOutsourcingProductBomSummary(outsourcingList, planEndDate);
                    this.buildPurchasingListProductBomSummary(purchasingList, planEndDate);
                }
            }
            if (CollectionUtil.isNotEmpty(outsourcingList)) {
                productPlanSummaryList.addAll(outsourcingList);
            }

            if (CollectionUtil.isNotEmpty(purchasingList)) {
                productPlanSummaryList.addAll(purchasingList);
            }
        }

        //处理生产计划订单
        List<ProductBomSummary> tempProduceList = productPlanSummaryList.stream().filter(p -> p.getMaterialAttr().equalsIgnoreCase("produce")).collect(toList());
        List<ProductBomSummary> produceList = tempProduceList.stream().collect(groupingBy(
                        product -> product.getMaterialBCode() + "," + product.getPlanEndDate(),
                        summingDouble(ProductBomSummary::getPlanQuantity)
                ))
                .entrySet()
                .stream()
                .map(entryObj -> buildProductBomSummary(entryObj))
                .filter(p -> p.getPlanQuantity() > 0)
                .sorted(Comparator.comparing(ProductBomSummary::getPlanEndDate))
                .collect(toList());
        productPlanSummaryList.removeIf(p -> p.getMaterialAttr().equalsIgnoreCase("produce"));
        productPlanSummaryList.addAll(produceList);
        this.buildProductPlanOrder();
    }

    /**
     * 构建生产计划
     *
     * @param entryObj
     * @return
     */
    private ProductBomSummary buildProductBomSummary(Map.Entry<String, Double> entryObj) {
        String[] split = entryObj.getKey().split(",");
        String materialBCode = split[0];
        LocalDate planEndDate = LocalDate.parse(split[1]);
        List<ProductBomSummary> produceList = productPlanSummaryList.stream().filter(p -> p.getMaterialAttr().equalsIgnoreCase("produce") && p.getMaterialBCode().equalsIgnoreCase(materialBCode) && p.getPlanEndDate().equals(planEndDate)).collect(toList());
        ProductBomSummary productBomSummary = produceList.stream()
                .min(Comparator.comparing(ProductBomSummary::getStartTime))
                .orElse(null);
        if (productBomSummary == null) {
            productBomSummary = new ProductBomSummary();
            productBomSummary.setPlanQuantity(0);
            return productBomSummary;
        }
        productBomSummary.setPlanQuantity(entryObj.getValue());
        Double produceDuration = produceList.stream().collect(summingDouble(ProductBomSummary::getProduceDuration));
        productBomSummary.setProduceDuration(produceDuration);
        ProductBomSummary productBomSummaryMax = produceList.stream()
                .max(Comparator.comparing(ProductBomSummary::getEndTime))
                .orElse(null);
        if (productBomSummaryMax != null) {
            LocalDateTime endTime = DateUtils.getLocalDateTime(planEndDate, sysConfigBslService.getWorkEndTime());
            LocalDateTime endTime1 = endTime.plusMinutes(-5);
            LocalDateTime endTime2 = endTime.plusMinutes(5);
            if (productBomSummaryMax.getEndTime().isAfter(endTime1) && productBomSummaryMax.getEndTime().isBefore(endTime2)) {
                productBomSummary.setEndTime(endTime);
            } else {
                productBomSummary.setEndTime(productBomSummaryMax.getEndTime());
            }
        } else {
            productBomSummary.setEndTime(productBomSummary.getStartTime().plusHours(produceDuration.longValue()));
        }
        return productBomSummary;

    }


    /**
     * 计算当前计划的开始结果时间,空闲时间为0时
     *
     * @param hasProductBomSummary       //已排成品数据
     * @param currentProductGroupSummary //当前成品数据
     * @param produceDuration            //生产时长
     */
    private void calculateCurrentStartEndTime(ProductBomSummary hasProductBomSummary, ProductBomSummary currentProductGroupSummary, double produceDuration) {
        LocalDateTime standStartTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), sysConfigBslService.getWorkStartTime());
        LocalDateTime endTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), sysConfigBslService.getWorkEndTime());
        LocalDateTime startTime;
        String afternoonWorkOffTime = "12:00:00";//午餐开始时间点
        String afternoonWorkOnTime = "13:00:00";//下午上班时间点
        LocalDateTime workOffTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), afternoonWorkOffTime); //午餐开始时间
        LocalDateTime workOnTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), afternoonWorkOnTime);//下午上班时间
        if (hasProductBomSummary == null) {
            //如果开始时间在13：00之前，结束时间在 13：00 之后，则减去1小时
            startTime = DateUtils.getLocalDateTimeMinusHours(endTime, produceDuration);
            if (startTime.isBefore(workOnTime) && endTime.isAfter(workOnTime)) {
                startTime = DateUtils.getLocalDateTimeMinusHours(startTime, 1.0);
            }
        } else {
            startTime = hasProductBomSummary.getEndTime();
            endTime = DateUtils.getLocalDateTimePlusHours(startTime, produceDuration);
            //如果开始时间在12：00之前，结束时间在 12：00 之后，则加上1小时
            if (startTime.isBefore(workOffTime) && endTime.isAfter(workOffTime)) {
                endTime = DateUtils.getLocalDateTimePlusHours(endTime, 1.0);
            }
        }
        if (startTime.isBefore(standStartTime)) {
            startTime = standStartTime;
        }

        currentProductGroupSummary.setStartTime(startTime);
        currentProductGroupSummary.setEndTime(endTime);
    }

    /**
     * 计算当前有产品在生产的当前产品计划的开始结果时间
     *
     * @param hasProductBomSummary       //已排成品数据
     * @param currentProductGroupSummary //当前成品数据
     * @param freeDuration               空闲时长
     * @param produceDuration            //生产时长
     */
    private void calculateHasProductCurrentStartEndTime(ProductBomSummary hasProductBomSummary, ProductBomSummary currentProductGroupSummary, double freeDuration, double produceDuration) {
        LocalDateTime standStartTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), sysConfigBslService.getWorkStartTime());
        LocalDateTime endTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), sysConfigBslService.getWorkEndTime());
        LocalDateTime startTime;
        String afternoonWorkOffTime = "12:00:00";//午餐开始时间点
        String afternoonWorkOnTime = "13:00:00";//下午上班时间点
        LocalDateTime workOffTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), afternoonWorkOffTime); //午餐开始时间
        LocalDateTime workOnTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), afternoonWorkOnTime);//下午上班时间
        if (hasProductBomSummary == null) {
            endTime = DateUtils.getLocalDateTimeMinusHours(endTime, freeDuration);
            //如果结束时间在12：00 到 13：00 之间，则减去1小时
            endTime = DateUtils.getLocalDateTimeMinusHours(endTime, freeDuration);
            if (endTime.isBefore(workOnTime)) {
                endTime = DateUtils.getLocalDateTimeMinusHours(endTime, 1.0);
            }

            //如果开始时间在13：00之前，结束时间在 13：00 之后，则减去1小时
            startTime = DateUtils.getLocalDateTimeMinusHours(endTime, produceDuration);
            if (startTime.isBefore(workOnTime) && endTime.isAfter(workOnTime)) {
                startTime = DateUtils.getLocalDateTimeMinusHours(startTime, 1.0);
            }
        } else {
            startTime = hasProductBomSummary.getEndTime();
            endTime = DateUtils.getLocalDateTimePlusHours(startTime, produceDuration);
            //如果开始时间在12：00之前，结束时间在 12：00 之后，则加上1小时
            if (startTime.isBefore(workOffTime) && endTime.isAfter(workOffTime)) {
                endTime = DateUtils.getLocalDateTimePlusHours(endTime, 1.0);
            }
        }

        if (startTime.isBefore(standStartTime)) {
            startTime = standStartTime;
        }
        currentProductGroupSummary.setStartTime(startTime);
        currentProductGroupSummary.setEndTime(endTime);
    }

    /**
     * 计算当前可完成生产的计划开始结果时间
     *
     * @param currentProductGroupSummary //当前成品数据
     * @param freeDuration               空闲时长
     * @param produceDuration            //生产时长
     */
    private void calculateFinishCurrentStartEndTime(ProductBomSummary currentProductGroupSummary, double freeDuration, double produceDuration) {
        //计算当前计划的开始结果时间
        LocalDateTime standStartTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), sysConfigBslService.getWorkStartTime());
        LocalDateTime endTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), sysConfigBslService.getWorkEndTime());

        String afternoonWorkOffTime = "12:00:00";//午餐开始时间点
        String afternoonWorkOnTime = "13:00:00";//下午上班时间点
        LocalDateTime workOffTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), afternoonWorkOffTime); //午餐开始时间
        LocalDateTime workOnTime = DateUtils.getLocalDateTime(currentProductGroupSummary.getPlanEndDate(), afternoonWorkOnTime);//下午上班时间
        //如果结束时间在13：00 之前，则减去1小时
        endTime = DateUtils.getLocalDateTimeMinusHours(endTime, freeDuration);
        if (endTime.isBefore(workOnTime)) {
            endTime = DateUtils.getLocalDateTimeMinusHours(endTime, 1.0);
        }

        //如果开始时间在13：00之前，结束时间在 13：00 之后，则减去1小时
        LocalDateTime startTime = DateUtils.getLocalDateTimeMinusHours(endTime, produceDuration);
        if (startTime.isBefore(workOnTime) && endTime.isAfter(workOnTime)) {
            startTime = DateUtils.getLocalDateTimeMinusHours(startTime, 1.0);
        }

        if (startTime.isBefore(standStartTime)) {
            startTime = standStartTime;
        }
        currentProductGroupSummary.setStartTime(startTime);
        currentProductGroupSummary.setEndTime(endTime);
    }

    private void buildProductPlanOrder() {
        if (CollectionUtil.isEmpty(productPlanSummaryList)) {
            return;
        }
        List<ProductBomSummary> productBomSummaryList = productPlanSummaryList.stream().sorted(Comparator.comparing(ProductBomSummary::getDeliveryDate)).collect(toList());
        for (ProductBomSummary productBomSummary : productBomSummaryList) {
            String materialAttr = productBomSummary.getMaterialAttr();
            switch (materialAttr) {
                case "purchasing":
                    this.buildPurchasePlanOrder(productBomSummary);
                    break;
                case "produce":
                    this.buildProductionPlanOrder(productBomSummary);
                    break;
                case "outsourcing":
                    this.buildOutsourcingPlanOrder(productBomSummary);
                    break;
                default:
                    break;
            }
        }
    }

    /**
     * 递归处理超出一个工作日的成生成品计划
     *
     * @param productBomSummary   要排序物实汇总
     * @param diffProduceDuration //超出的工时数（小时）
     */
    private void processingProductPlanSummary(ProductBomSummary productBomSummary, double diffProduceDuration, double diffPlanQuantity) {
        List<Long> equipmentList = productBomSummary.getEquipmentList();
        LocalDate planEndDate = productBomSummary.getPlanEndDate();
        String materialBCode = productBomSummary.getMaterialBCode();
        //生产成品占用日期和最空小空闲map
        Map<LocalDate, Double> occupyMap = new HashMap<>();
        //统计当前计划日期之前的当前生产成品的总空闲时长
        double freeDurationSum = fetchEffectiveDurationSum(materialBCode, planEndDate, equipmentList, occupyMap);
        if (freeDurationSum >= diffProduceDuration) {
            processingFullProductPlanSummary(productBomSummary, diffProduceDuration, diffPlanQuantity, occupyMap);
        } else {
            List<LocalDate> dateList = occupyMap.keySet().stream().collect(Collectors.toList());
            processingNonProductPlanSummary(productBomSummary, diffProduceDuration, diffPlanQuantity, dateList);
        }
    }

    /**
     * 处理能够在占用设备区排完计划的排程
     *
     * @param productBomSummary   在制产品对象
     * @param diffProduceDuration 剩余生产时长
     * @param diffPlanQuantity    剩余生产数量
     * @param occupyMap           生产成品占用日期和最空小空闲map
     */
    private void processingFullProductPlanSummary(ProductBomSummary productBomSummary, double diffProduceDuration, double diffPlanQuantity, Map<LocalDate, Double> occupyMap) {
        List<Long> equipmentList = productBomSummary.getEquipmentList();
        String materialBCode = productBomSummary.getMaterialBCode();
        double produceAvgDuration = productBomSummary.getProduceAvgDuration();
        for (Map.Entry<LocalDate, Double> mapEntry : occupyMap.entrySet()) {
            LocalDate occupyDate = mapEntry.getKey();
            Double effectiveDuration = mapEntry.getValue();
            ProductBomSummary currentProductGroupSummary = new ProductBomSummary();
            BeanUtils.copyProperties(productBomSummary, currentProductGroupSummary);
            currentProductGroupSummary.setPlanEndDate(occupyDate);
            ProductBomSummary hasProductBomSummary = productPlanSummaryList.stream().filter(p -> p.getMaterialBCode().equalsIgnoreCase(materialBCode) && p.getPlanEndDate().isEqual(occupyDate)).sorted(Comparator.comparing(ProductBomSummary::getEndTime).reversed()).findFirst().orElse(null);
            if (effectiveDuration > diffProduceDuration) {
                currentProductGroupSummary.setPlanQuantity(diffPlanQuantity);
                currentProductGroupSummary.setProduceDuration(diffProduceDuration);
                currentProductGroupSummary.setFreeDuration(effectiveDuration - diffProduceDuration);
                calculateHasProductCurrentStartEndTime(hasProductBomSummary, currentProductGroupSummary, currentProductGroupSummary.getFreeDuration(), diffProduceDuration);
                productPlanSummaryList.add(currentProductGroupSummary);
                this.adjustEquipmentOccupy(materialBCode, occupyDate, equipmentList, diffProduceDuration);
                break;
            }

            //计算超出的工时数（小时）
            double diffProduceDuration1 = diffProduceDuration - effectiveDuration;
            //计算超出的数量  超出数量 = 超出时长 / 每件时长  结果上取整
            double diffPlanQuantity1 = TypeUtils.ceilNumber((diffProduceDuration1 / produceAvgDuration) * 100 / 100.0);
            //当前天计划生产数量 = 计划数量-超出的数量
            double currentPlanQuantity = diffPlanQuantity - diffPlanQuantity1;
            currentProductGroupSummary.setPlanQuantity(currentPlanQuantity);
            currentProductGroupSummary.setProduceDuration(effectiveDuration);
            currentProductGroupSummary.setFreeDuration(0);
            calculateCurrentStartEndTime(hasProductBomSummary, currentProductGroupSummary, effectiveDuration);
            productPlanSummaryList.add(currentProductGroupSummary);
            this.adjustEquipmentOccupy(materialBCode, occupyDate, equipmentList, effectiveDuration);
            diffProduceDuration = diffProduceDuration1;
            diffPlanQuantity = diffPlanQuantity1;
        }
    }

    /**
     * 处理未排完计划在非占用设备区进行排程
     *
     * @param productBomSummary   在制产品对象
     * @param diffProduceDuration 剩余生产时长
     * @param diffPlanQuantity    剩余生产数量
     * @param dateList            已生产占用日期列表
     */
    private void processingNonProductPlanSummary(ProductBomSummary productBomSummary, double diffProduceDuration, double diffPlanQuantity, List<LocalDate> dateList) {
        List<Long> equipmentList = productBomSummary.getEquipmentList();
        LocalDate planEndDate = productBomSummary.getPlanEndDate();
        String materialBCode = productBomSummary.getMaterialBCode();
        double produceAvgDuration = productBomSummary.getProduceAvgDuration();
        double produceLeadTime = productBomSummary.getProduceLeadTime();
        Map<LocalDate, Double> occupyMap = new HashMap<>();
        buildNonProducedDateList(dateList, planEndDate, equipmentList, occupyMap);
        if (occupyMap.size() < 1) {
            String msg = String.format("生产背番为：%s产品生产时间不足，计划与排程失败！", materialBCode);
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        //获取第一个Entry对象
        Map.Entry<LocalDate, Double> mapEntry = occupyMap.entrySet().iterator().next();
        LocalDate occupyDate = mapEntry.getKey();
        Double effectiveDuration = mapEntry.getValue();
        ProductBomSummary currentProductGroupSummary = new ProductBomSummary();
        BeanUtils.copyProperties(productBomSummary, currentProductGroupSummary);
        currentProductGroupSummary.setPlanEndDate(occupyDate);
        double totalProduceDuration = diffProduceDuration + produceLeadTime;
        if (effectiveDuration > totalProduceDuration) {
            currentProductGroupSummary.setPlanQuantity(diffPlanQuantity);
            totalProduceDuration = TypeUtils.ceilNumber(totalProduceDuration * 100) / 100.0;
            double freeDuration = TypeUtils.floorNumber((effectiveDuration - totalProduceDuration) * 100) / 100.0;
            currentProductGroupSummary.setProduceDuration(totalProduceDuration);
            currentProductGroupSummary.setFreeDuration(freeDuration);
            calculateFinishCurrentStartEndTime(currentProductGroupSummary, currentProductGroupSummary.getFreeDuration(), totalProduceDuration);
            productPlanSummaryList.add(currentProductGroupSummary);
            this.adjustEquipmentOccupy(materialBCode, occupyDate, equipmentList, totalProduceDuration);
        } else {
            //计算超出的工时数（小时）
            double diffProduceDuration1 = totalProduceDuration - effectiveDuration;
            //计算超出的数量  超出数量 = 超出时长 / 每件时长  结果上取整
            double diffPlanQuantity1 = TypeUtils.ceilNumber((diffProduceDuration1 / produceAvgDuration) * 100 / 100.0);
            //当前天计划生产数量 = 计划数量-超出的数量
            double currentPlanQuantity = diffPlanQuantity - diffPlanQuantity1;
            currentProductGroupSummary.setPlanQuantity(currentPlanQuantity);
            currentProductGroupSummary.setProduceDuration(effectiveDuration);
            currentProductGroupSummary.setFreeDuration(0);
            calculateFinishCurrentStartEndTime(currentProductGroupSummary, currentProductGroupSummary.getFreeDuration(), effectiveDuration);
            productPlanSummaryList.add(currentProductGroupSummary);
            this.adjustEquipmentOccupy(materialBCode, occupyDate, equipmentList, effectiveDuration);
            diffProduceDuration = diffProduceDuration1;
            diffPlanQuantity = diffPlanQuantity1;
            this.processingProductPlanSummary(productBomSummary, diffProduceDuration, diffPlanQuantity);
        }
    }

    /**
     * 构建非占用设备列表
     *
     * @param dateList      占用日期列表
     * @param occupyDate    占用日期
     * @param equipmentList 设备列表
     * @param occupyMap     构建Map
     */
    private void buildNonProducedDateList(List<LocalDate> dateList, LocalDate occupyDate, List<Long> equipmentList, Map<LocalDate, Double> occupyMap) {
        List<LocalDate> tempDateList;
        if (CollectionUtil.isNotEmpty(dateList)) {
            tempDateList = this.equipmentOccupyBslService.getEquipmentOccupyList().stream()
                    .filter(eo -> !dateList.contains(eo.getOccupyDate()) && eo.getOccupyDate().isBefore(occupyDate) && eo.getEffectiveDuration() > 0)
                    .map(EquipmentOccupy::getOccupyDate).distinct().collect(toList());
        } else {
            tempDateList = this.equipmentOccupyBslService.getEquipmentOccupyList().stream().filter(eo -> eo.getOccupyDate().isBefore(occupyDate) && eo.getEffectiveDuration() > 0)
                    .map(EquipmentOccupy::getOccupyDate).distinct().collect(toList());
        }

        if (CollectionUtil.isEmpty(tempDateList)) {
            return;
        }

        Collections.sort(tempDateList, Collections.reverseOrder());
        for (LocalDate d : tempDateList) {
//            Boolean isHoliday = this.workCalendarBslService.checkHoliday(d);
//            if(isHoliday){
//                continue;
//            }
            double effectiveDurationMin = fetchEquipmentOccupyList(equipmentList, d).stream().mapToDouble(EquipmentOccupy::getEffectiveDuration).min().getAsDouble();
            if (effectiveDurationMin > 0) {
                occupyMap.put(d, effectiveDurationMin);
            }
        }
    }

    /**
     * 获取已生产materialBCode 物料设备最小空闲时长和
     *
     * @param materialBCode 生产物料
     * @param occupyDate    占用日期
     * @param equipmentList materialBCode占用的设备组
     * @param occupyMap     materialBCode占用日期和最空小空闲map
     * @return materialBCode 物料设备最小空闲时长和
     */
    private double fetchEffectiveDurationSum(String materialBCode, LocalDate occupyDate, List<Long> equipmentList, Map<LocalDate, Double> occupyMap) {
        List<LocalDate> dateList = this.equipmentOccupyBslService.getEquipmentOccupyList().stream()
                .filter(eo -> materialBCode.equalsIgnoreCase(eo.getMaterialBCode()) && eo.getOccupyDate().isBefore(occupyDate) && eo.getEffectiveDuration() > 0)
                .map(EquipmentOccupy::getOccupyDate).distinct().collect(toList());
        double effectiveDuration = 0;
        Collections.sort(dateList, Collections.reverseOrder());
        for (LocalDate d : dateList) {
            double effectiveDurationMin = fetchEquipmentOccupyList(equipmentList, d).stream().mapToDouble(EquipmentOccupy::getEffectiveDuration).min().getAsDouble();
            occupyMap.put(d, effectiveDurationMin);
            effectiveDuration += effectiveDurationMin;
        }
        return effectiveDuration;
    }

    /**
     * 获取设备占用列表
     *
     * @param equipmentList 设备列表
     * @param occupyDate    占用日期
     * @return 返回占用列表
     */
    private List<EquipmentOccupy> fetchEquipmentOccupyList(List<Long> equipmentList, LocalDate occupyDate) {
        List<EquipmentOccupy> equipmentOccupyList = new ArrayList<>();
        equipmentList.forEach(id -> {
            EquipmentOccupy equipmentOccupy = this.equipmentOccupyBslService.getEquipmentOccupyList().stream().filter(eo -> eo.getId().longValue() == id.longValue() && eo.getOccupyDate().isEqual(occupyDate)).findFirst().orElse(null);
            if (equipmentOccupy == null) {
                //添加当前生产成品设备的占用时长和空闲时长
                equipmentOccupy = new EquipmentOccupy();
                equipmentOccupy.setOccupyDate(occupyDate);
                equipmentOccupy.setId(id);
                equipmentOccupy.setStopped(1);
                equipmentOccupy.setOccupyDuration(0);
                equipmentOccupy.setEffectiveDuration(this.sysConfigBslService.getWorkHours());
            }

            equipmentOccupyList.add(equipmentOccupy);
        });
        return equipmentOccupyList;
    }

    /**
     * 调整设备占用及设备空闲时长
     *
     * @param materialBCode  占用设备物料
     * @param occupyDate     占用日期
     * @param equipmentList  需要调整的设备ids集合
     * @param occupyDuration 占用时长
     * @return
     */
    private void adjustEquipmentOccupy(String materialBCode, LocalDate occupyDate, List<Long> equipmentList, double occupyDuration) {
        equipmentList.forEach(id -> {
            EquipmentOccupy equipmentOccupy = this.equipmentOccupyBslService.getEquipmentOccupyList().stream().filter(eo -> eo.getId().longValue() == id.longValue() && eo.getOccupyDate().isEqual(occupyDate)).findFirst().orElse(null);
            if (equipmentOccupy == null) {
                //添加当前生产成品设备的占用时长和空闲时长
                equipmentOccupy = new EquipmentOccupy();
                equipmentOccupy.setId(id);
                equipmentOccupy.setStopped(1);
                equipmentOccupy.setOccupyDuration(occupyDuration);
                equipmentOccupy.setEffectiveDuration(this.sysConfigBslService.getWorkHours() - occupyDuration);
            } else {
                //修改当前生产成品设备的占用时长和空闲时长
                equipmentOccupy.setOccupyDuration(equipmentOccupy.getOccupyDuration() + occupyDuration);
                equipmentOccupy.setEffectiveDuration(this.sysConfigBslService.getWorkHours() - equipmentOccupy.getOccupyDuration());
            }
            equipmentOccupy.setMaterialBCode(materialBCode);
            equipmentOccupy.setOccupyDate(occupyDate);
            //维护设备占用列表
            this.equipmentOccupyBslService.getEquipmentOccupyList().removeIf(e -> e.getId().longValue() == id.longValue() && e.getOccupyDate().isEqual(occupyDate));
            this.equipmentOccupyBslService.getEquipmentOccupyList().add(equipmentOccupy);
        });
    }

    /**
     * 将生产成品按照生产设备不能同时使用分组
     *
     * @param produceList 生产产品列表
     * @return 返回互斥分组集合
     */
    private List<Map<String, List<Long>>> exclusionGroupProductBomSummary(List<ProductBomSummary> produceList) {
        Map<String, List<Long>> map = new HashMap<>();
        produceList.forEach(p -> {
            map.put(p.getMaterialBCode(), p.getEquipmentList());
        });
        List<Map<String, List<Long>>> listMap = new ArrayList<>();
        for (Map.Entry<String, List<Long>> entry : map.entrySet()) {
            List<Long> value = entry.getValue();
            String key = entry.getKey();
            Boolean isIncluded = true;
            for (Map<String, List<Long>> lMap : listMap) {
                if (!hasIntersection(lMap, value)) {
                    lMap.put(key, value);
                    isIncluded = false;
                    break;
                }
            }

            if (isIncluded) {
                Map<String, List<Long>> tempMap = new HashMap<>();
                tempMap.put(key, value);
                listMap.add(tempMap);
            }
        }
        return listMap;
    }

    /**
     * @param map       map集合
     * @param knownList 比较的列表
     * @return 包涵返true, 不包涵返false
     */
    private static boolean hasIntersection(Map<String, List<Long>> map, List<Long> knownList) {
        for (List<Long> values : map.values()) {
            for (Long value : values) {
                if (knownList.contains(value)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 生产物料按计划完成日期从早到晚排序
     *
     * @param productBomSummaryList 生产物料列表
     * @return 返回排序后的map
     */
    private Map<LocalDate, List<ProductBomSummary>> sortProductBomSummaryByPlanEndDate(List<ProductBomSummary> productBomSummaryList) {
        //将生产、采购、委外计划汇总量，按以计划日期为key,每天的汇总列表为value 组成Map<LocalDate,List<ProductBomSummary>>
        Map<LocalDate, List<ProductBomSummary>> productBomSummaryMap = productBomSummaryList.stream().collect(groupingBy(ProductBomSummary::getPlanEndDate));
        //将Map进行按key 排序
        Map<LocalDate, List<ProductBomSummary>> sorteProductSummaryMap = productBomSummaryMap.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByKey())
                .collect(
                        LinkedHashMap::new,
                        (result, entry) -> result.put(entry.getKey(), entry.getValue()),
                        LinkedHashMap::putAll
                );

        return sorteProductSummaryMap;
    }

    /**
     * 构建委外计划
     *
     * @param productBomSummary 计划物料层级
     */
    private void buildOutsourcingPlanOrder(ProductBomSummary productBomSummary) {
        double planQuantity = productBomSummary.getPlanQuantity();
        String materialBCode = productBomSummary.getMaterialBCode();
        LocalDate planEndDate = productBomSummary.getPlanEndDate();
        int outsourcingPeriod = productBomSummary.getOutsourcingPeriod() + 1;
        LocalDate planStartDate = planEndDate.plusDays(-outsourcingPeriod);
        LocalDate today = LocalDate.now();
        while (planStartDate.isBefore(today)) {
            String msg = String.format("生产背番为：%s产品委外开始时间%s不满足生产要求，计划与排程失败！", materialBCode, planStartDate);
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        String startDate = DateUtils.getLocalDateFormat(planStartDate, "yyyy-MM-dd 00:00:00");
        String endDate = DateUtils.getLocalDateFormat(planEndDate, "yyyy-MM-dd 00:00:00");
        String date = DateUtils.getDateFormat(new Date(), "yyyy-MM-dd 00:00:00");


        Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        JSONObject values = new JSONObject();
        String name = String.format("%s，背番为%s的委外计划订单", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"), material.getBcode());
        int year = LocalDate.now().getYear();
        String planCode = String.format("O_PLAN_ORDER_%s", year);
        values.put("name", name);
        values.put("code", batchNumberDslService.getNextVal(planCode));
        values.put("planType", this.planType);
        values.put("status", 0);
        values.put("routingNumber", "");
        values.put("date", date);
        values.put("startDate", startDate);
        values.put("endDate", endDate);
        values.put("material_id", material.getId());
        values.put("material_pcode", material.getPcode());
        values.put("material_bcode", material.getBcode());
        values.put("quantity", planQuantity);
        values.put("material_spec", material.getSpec());
        values.put("material_model", material.getModel());
        values.put("mrpId", this.mrpLogBslService.getMrpId());
        JSONObject productionPlan = this.logicInstanceDslService.add(ModelType.Logic.getCode(), "OutsourcingPlanOrder", name, values);
        Integer mainPlanId = productionPlan.getInteger("id");

        Map<String, Double> materialMap = new HashMap<>();
        Map<String, Double> productMap = new HashMap<>();
        //构造产品的原料和副产品信息
        buildProductMap(materialBCode, materialMap, planQuantity, productMap);
        //原材料明细
        this.buildPlanOrderSub(materialMap, "1", "OutsourcingPlanOrder", mainPlanId);
        //副产品明细
        this.buildPlanOrderSub(productMap, "2", "OutsourcingPlanOrder", mainPlanId);
    }

    /**
     * 构建生产订划订单明细
     *
     * @param planOrderSubMap 明细数据
     * @param subType         1:原料料，2：副产品
     * @param mainPlanId      主信息Id
     */
    private void buildPlanOrderSub(Map<String, Double> planOrderSubMap, String subType, String className, Integer mainPlanId) {
        planOrderSubMap.forEach((key, value) -> {
            Material tempMaterial = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(key)).findFirst().orElse(null);
            JSONObject hierInstanceJson = new JSONObject();
            hierInstanceJson.put("parentId", "root");
            hierInstanceJson.put("containerId", mainPlanId);
            hierInstanceJson.put("containerModel", ModelType.Logic.getCode());
            hierInstanceJson.put("className", className);
            JSONObject subValues = new JSONObject();
            subValues.put("material_id", tempMaterial.getId());
            subValues.put("material_pcode", tempMaterial.getPcode());
            subValues.put("material_bcode", tempMaterial.getBcode());
            subValues.put("material_spec", tempMaterial.getSpec());
            subValues.put("material_model", tempMaterial.getModel());
            subValues.put("material_unit", tempMaterial.getUnit());
            subValues.put("quantity", value);
            subValues.put("subType", subType); //1:原料料，2：副产品
            hierInstanceJson.put("values", subValues);
            this.hierInstanceDslService.addChild(hierInstanceJson);
        });
    }

    /**
     * 构建采购生产计划
     *
     * @param productBomSummary 计划物料层级
     */
    private void buildPurchasePlanOrder(ProductBomSummary productBomSummary) {
        double planQuantity = productBomSummary.getPlanQuantity();
        String materialBCode = productBomSummary.getMaterialBCode();
        int purchasingPeriod = productBomSummary.getPurchasingPeriod() + 1;
        LocalDate planEndDate = productBomSummary.getPlanEndDate();
        LocalDate planStartDate = planEndDate.plusDays(-purchasingPeriod);

        LocalDate today = LocalDate.now();
        while (planStartDate.isBefore(today)) {
            String msg = String.format("生产背番为：%s产品采购开始时间%s不满足生产要求，计划与排程失败！", materialBCode, planStartDate);
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        String startDate = DateUtils.getLocalDateFormat(planStartDate, "yyyy-MM-dd 00:00:00");
        String endDate = DateUtils.getLocalDateFormat(planEndDate, "yyyy-MM-dd 00:00:00");
        String currentDate = DateUtils.getDateFormat(new Date(), "yyyy-MM-dd 00:00:00");
        Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        JSONObject values = new JSONObject();
        String name = String.format("%s，背番为%s的采购计划订单", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"), material.getBcode());
        int year = LocalDate.now().getYear();
        String planCode = String.format("G_PLAN_ORDER_%s", year);
        values.put("name", name);
        values.put("code", batchNumberDslService.getNextVal(planCode));
        values.put("planType", this.planType);
        values.put("status", 0);
        values.put("routingNumber", "");
        values.put("date", currentDate);
        values.put("startDate", startDate);
        values.put("endDate", endDate);
        values.put("material_id", material.getId());
        values.put("material_pcode", material.getPcode());
        values.put("material_bcode", material.getBcode());
        values.put("quantity", planQuantity);
        values.put("material_spec", material.getSpec());
        values.put("material_model", material.getModel());
        values.put("mrpId", this.mrpLogBslService.getMrpId());
        this.logicInstanceDslService.add(ModelType.Logic.getCode(), "PurchasePlanOrder", name, values);
    }

    /**
     * 构建生产计划
     *
     * @param productBomSummary 计划物料层级
     */
    private void buildProductionPlanOrder(ProductBomSummary productBomSummary) {
        double planQuantity = productBomSummary.getPlanQuantity();
        String materialBCode = productBomSummary.getMaterialBCode();
        LocalDateTime startTime = productBomSummary.getStartTime();
        LocalDateTime endTime = productBomSummary.getEndTime();
        LocalDate today = LocalDate.now();
        while (startTime.toLocalDate().isBefore(today)) {
            if (materialBCode.equalsIgnoreCase("T080-0")) {
                System.out.println("**********************" + materialBCode);
            }

            String msg = String.format("生产背番为：%s产品生产开始时间%s不满足销售要求，计划与排程失败！", materialBCode, startTime.toLocalDate());
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }
        double produceDuration = productBomSummary.getProduceDuration();
        String startDate = DateUtils.getLocalDateFormat(startTime, "yyyy-MM-dd HH:mm:ss");
        String endDate = DateUtils.getLocalDateFormat(endTime, "yyyy-MM-dd HH:mm:ss");
        Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        JSONObject values = new JSONObject();

        String name = String.format("%s，背番为%s的生产计划订单", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"), material.getBcode());
        int year = LocalDate.now().getYear();
        String planCode = String.format("P_PLAN_ORDER_%s", year);

        JSONObject wppJson = workProcessPathBslService.getWppEquipList(materialBCode);


        values.put("name", name);
        values.put("code", batchNumberDslService.getNextVal(planCode));
        values.put("planType", this.planType);
        values.put("status", 0);
        values.put("routingNumber", "");
        values.put("material_id", material.getId());
        values.put("material_pcode", material.getPcode());
        values.put("material_bcode", material.getBcode());
        values.put("quantity", planQuantity);
        values.put("material_spec", material.getSpec());
        values.put("material_model", material.getModel());
        values.put("material_unit", material.getUnit());
        if (wppJson != null) {
            values.put("equipIds", wppJson.get("equipIds"));
            values.put("equipCodes", wppJson.get("equipCodes"));
            values.put("equipNames", wppJson.get("equipNames"));
            values.put("wppId", wppJson.getLong("wppId"));
            values.put("wppCode", wppJson.get("wppCode"));
            values.put("wppName", wppJson.get("wppName"));
            values.put("lineBodyCode", wppJson.get("lineBodyCode"));
            values.put("lineBodyName", wppJson.get("lineBodyName"));
        } else {
            values.put("equipIds", new JSONArray());
            values.put("equipCodes", new JSONArray());
            values.put("equipNames", new JSONArray());
            values.put("wppId", null);
            values.put("wppCode", "");
            values.put("wppName", "");
            values.put("lineBodyCode", "");
            values.put("lineBodyName", "");
        }
        values.put("startDate", startDate);
        values.put("endDate", endDate);
        values.put("workHours", produceDuration);
        values.put("mrpId", this.mrpLogBslService.getMrpId());
        JSONObject productionPlan = this.logicInstanceDslService.add(ModelType.Logic.getCode(), "ProductionPlanOrder", name, values);
        Integer mainPlanId = productionPlan.getInteger("id");

        Map<String, Double> materialMap = new HashMap<>();
        Map<String, Double> productMap = new HashMap<>();
        //构造产品的原料和副产品信息
        buildProductMap(materialBCode, materialMap, planQuantity, productMap);
        //原材料明细
        this.buildPlanOrderSub(materialMap, "1", "ProductionPlanOrder", mainPlanId);
        //副产品明细
        this.buildPlanOrderSub(productMap, "2", "ProductionPlanOrder", mainPlanId);
    }

    /**
     * 构造产品的原料和副产品信息
     *
     * @param materialBCode 产品背番
     * @param materialMap   原料map
     * @param planQuantity  产品数量
     * @param productMap    副产品map
     */
    public void buildProductMap(String materialBCode, Map<String, Double> materialMap, double planQuantity, Map<String, Double> productMap) {
        ProductBomMaster productBomMaster = this.productBomBslService.getProductBomList().stream().filter(d -> materialBCode.equalsIgnoreCase(d.getMaterialBCode())).findFirst().orElse(null);
        if (productBomMaster == null) {
            return;
        }
        double quantity = productBomMaster.getQuantity();
        List<ProductBomSub> productBomSubList1 = productBomMaster.getProductBomSubList();
        if (CollectionUtil.isNotEmpty(productBomSubList1)) {
            productBomSubList1.forEach(pb -> {
                //副产品BOM倍量
                double subQuantity = pb.getQuantity();
                //产成品倍数 = 计划量/物料倍量
                double quantityTimes = TypeUtils.ceilNumber(planQuantity * 100 / quantity) / 100.0;
                //副产品数量
                double subPlanQuantity = quantityTimes * subQuantity;
                productMap.put(pb.getMaterialBCode(), subPlanQuantity * (1 + pb.getLossRate()));
            });

        }

        List<ProductBomSub> materialBomSubList = productBomMaster.getMaterialBomSubList();
        if (CollectionUtil.isNotEmpty(materialBomSubList)) {
            materialBomSubList.forEach(pb -> {
                //副产品BOM倍量
                double subQuantity = pb.getQuantity();
                //产成品倍数 = 计划量/物料倍量
                double quantityTimes = TypeUtils.ceilNumber(planQuantity * 100 / quantity) / 100.0;
                //副产品数量
                double subPlanQuantity = quantityTimes * subQuantity;
                materialMap.put(pb.getMaterialBCode(), subPlanQuantity * (1 + pb.getLossRate()));
            });
        }
    }

    private void buildPurchasingListProductBomSummary(List<ProductBomSummary> purchasingList, LocalDate planEndDate) {
        List<ProductBomLevel> productBomLevels = this.productBomBslService.getProductBomLevels();
        List<ProductBomLevel> purchasingProductBomLevelList = productBomLevels.stream().filter(pb -> pb.getMaterialAttr().equalsIgnoreCase("purchasing"))
                .sorted(Comparator.comparing(ProductBomLevel::getLevel)).collect(toList());
        for (ProductBomLevel purchasingLevel : purchasingProductBomLevelList) {
            String mbCode = purchasingLevel.getMaterialBCode();
            ProductBomSummary purchasingListSummary = purchasingList.stream().filter(o -> o.getMaterialBCode().equalsIgnoreCase(mbCode)).findFirst().orElse(null);
            if (purchasingListSummary == null) {
                continue;
            }

            if (purchasingLevel.getParentBCode() == null) {
                purchasingLevel.setPlanEndDate(planEndDate);
            } else {
                System.out.println("===============================" + purchasingLevel.getParentBCode());
                List<ProductBomSummary> productBomSummaryList = productPlanSummaryList.stream().filter(pp -> pp.getMaterialBCode().equalsIgnoreCase(purchasingLevel.getParentBCode()) && pp.getPlanEndDate().isEqual(planEndDate)).collect(toList());
                if (CollectionUtil.isNotEmpty(productBomSummaryList)) {
                    LocalDate purchasingPlanEndDate = productBomSummaryList.stream().map(ProductBomSummary::getPlanEndDate).min(LocalDate::compareTo).get();
                    if (purchasingPlanEndDate == null) {
                        purchasingListSummary.setPlanEndDate(planEndDate);
                    } else {
                        purchasingListSummary.setPlanEndDate(purchasingPlanEndDate);
                    }
                } else {
                    continue;
                }
            }

            productPlanSummaryList.add(purchasingListSummary);
            purchasingList.removeIf(o -> o.getMaterialBCode().equalsIgnoreCase(mbCode));
        }
    }

    /**
     * 构建委外生产对象
     *
     * @param outsourcingList 委外生产列表
     * @param planEndDate     委外到货日期
     */
    private void buildOutsourcingProductBomSummary(List<ProductBomSummary> outsourcingList, LocalDate planEndDate) {
        List<ProductBomLevel> productBomLevels = this.productBomBslService.getProductBomLevels();
        //委外bom 列表
        List<ProductBomLevel> outsourcingProductBomLevelList = productBomLevels.stream().filter(pb -> pb.getMaterialAttr().equalsIgnoreCase("outsourcing"))
                .sorted(Comparator.comparing(ProductBomLevel::getLevel)).collect(toList());
        for (ProductBomLevel outsourcingLevel : outsourcingProductBomLevelList) {
            String mbCode = outsourcingLevel.getMaterialBCode();
            ProductBomSummary outsourcingSummary = outsourcingList.stream().filter(o -> o.getMaterialBCode().equalsIgnoreCase(mbCode)).findFirst().orElse(null);
            if (outsourcingSummary == null) {
                continue;
            }
            if (outsourcingLevel.getParentBCode() == null) {
                outsourcingSummary.setPlanEndDate(planEndDate);
            } else {
                List<ProductBomSummary> productBomSummaryList = productPlanSummaryList.stream().filter(pp -> pp.getMaterialBCode().equalsIgnoreCase(outsourcingLevel.getParentBCode()) && pp.getDeliveryDate().isEqual(planEndDate)).collect(toList());
                if (CollectionUtil.isEmpty(productBomSummaryList)) {
                    outsourcingSummary.setPlanEndDate(planEndDate);
                } else {
                    LocalDate outsourcingPlanEndDate = productBomSummaryList.stream().filter(pp -> pp.getMaterialBCode().equalsIgnoreCase(outsourcingLevel.getParentBCode()) && pp.getPlanEndDate().isEqual(planEndDate))
                            .map(ProductBomSummary::getPlanEndDate).min(LocalDate::compareTo).get();
                    outsourcingSummary.setPlanEndDate(outsourcingPlanEndDate);
                }
            }
            productPlanSummaryList.add(outsourcingSummary);
            outsourcingList.removeIf(o -> o.getMaterialBCode().equalsIgnoreCase(mbCode));
        }
    }

    /**
     * 构建物料计划层级列表
     *
     * @return 物料计划层级列表
     */
    private void buildSalePlanBomLevels(List<ProductBomLevel> productBomLevels) {
        LocalDate deliveryDate = this.saleOrderBslService.getCurrentSaleOrderSummary().getDeliveryDate();
        //根据BOM 计算订单产品所需自制生产数量、委外生产数量、采购原料数量并分别生成对应计划
        Double saleQuantity = Double.valueOf(this.saleOrderBslService.getCurrentSaleOrderSummary().getSaleQuantity());
        System.out.println("****************bom 层级：" + productBomLevels);
        for (ProductBomLevel productBomLevel : productBomLevels) {
            productBomLevel.setDeliveryDate(deliveryDate);
            productBomLevel.setPlanEndDate(deliveryDate);
            String parentBCode = productBomLevel.getParentBCode();

            //父级物料需求量
            double parentDemandQuantity;
            ProductBomLevel tempProductBomLevel = productBomLevels.stream().filter(pbl -> pbl.getMaterialBCode().equalsIgnoreCase(parentBCode)).findFirst().orElse(null);
            if (tempProductBomLevel == null) {
                parentDemandQuantity = saleQuantity;
            } else if (TypeUtils.ceilNumber(tempProductBomLevel.getPlanQuantity()) == 0) {
                productBomLevel.setPlanQuantity(0.0);
                continue;
            } else {
                double quantity = tempProductBomLevel.getQuantity();
                int tempQuantity = TypeUtils.ceilNumber(quantity * 100);
                if (tempQuantity == 0) {
                    String msg = String.format("背番：%s的BOM数量为0，MRP运算失败！", tempProductBomLevel.getMaterialBCode());
                    this.mrpLogBslService.buildMrpLog(2, msg);
                    throw new BusinessException("999999", msg);
                }
                parentDemandQuantity = TypeUtils.ceilNumber(tempProductBomLevel.getPlanQuantity() * 100 / quantity) / 100.0;
            }
            if (productBomLevel.getMaterialBCode().equalsIgnoreCase("A-T116-1S")) {
                System.out.println("##############################" + productBomLevel);
            }
            this.buildProductBOMLevel(parentDemandQuantity, productBomLevel);
        }
    }

    /**
     * 根据成品BOM层级和需求数量（parentDemandQuantity），计算计划数量（planQuantity）
     *
     * @param parentDemandQuantity 父BOM计划数量
     * @param productBomLevel      产品BOM层级列表
     */
    public void buildProductBOMLevel(Double parentDemandQuantity, ProductBomLevel productBomLevel) {
        String bCode = productBomLevel.getMaterialBCode();
        if (bCode.equalsIgnoreCase("94223-80600-HKSS")) {
            System.out.println("##############################" + productBomLevel);
        }
        //1、获取实际库存
        Double stockQuantity = this.stockDetailBslService.getStockMap().get(bCode);
        if (stockQuantity == null) {
            stockQuantity = 0.0;
        }

        //2、获取物料信息
        Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(bCode)).findFirst().orElse(null);
        if (material == null) {
            String msg = String.format("没有维护背番：%s的物料，获取安全库存报错", bCode);
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        Double planDemandQuantity = productBomLevel.getQuantity() * parentDemandQuantity;
        planDemandQuantity = TypeUtils.ceilNumber(planDemandQuantity * (1 + productBomLevel.getLossRate())) * 1.0;

        //3、检查该成品是否为试制成品
        SaleOrderSub saleOrderSub = this.saleOrderBslService.getTrialOrderSubs().stream().filter(o -> o.getMaterialBCode().equalsIgnoreCase(bCode) && o.getDeliveryDate().isEqual(productBomLevel.getDeliveryDate())).findFirst().orElse(null);
        if (saleOrderSub != null) {
            this.stockDetailBslService.getStockMap().put(bCode, stockQuantity + planDemandQuantity);
            String msg = String.format("背番：%s为试制产品，试制量为%s，试制时长%s", bCode, planDemandQuantity, saleOrderSub.getTrialDuration());
            this.mrpLogBslService.buildMrpLog(1, msg);
            productBomLevel.setPlanQuantity(planDemandQuantity);
            return;
        }
        //4、检查需求量： safeStock + subQuantity 与 实际库存量（供给）stockQuantity 差，如果小于等0 说明库存充足，无需创建任何计划
        Double planQuantity = material.getSafeStock() + planDemandQuantity - stockQuantity;  //计算计划生产量
        //库存充足，无需创建任何计划
        if (planQuantity <= 0) {
            this.stockDetailBslService.getStockMap().put(bCode, stockQuantity - planDemandQuantity);
            String msg = String.format("背番：%s的产品生产期间理论在库数量充足，无需创建计划订单", bCode);
            this.mrpLogBslService.buildMrpLog(1, msg);
            productBomLevel.setPlanQuantity(0.0);
            return;
        }
        //5、计算采购、生产、委外计划数量
        calculatePlanQuantity(productBomLevel, planQuantity, stockQuantity, planDemandQuantity, material);
    }

    //计算采购、生产、委外计划数量
    private void calculatePlanQuantity(ProductBomLevel productBomLevel, Double planQuantity, Double stockQuantity, Double planDemandQuantity, Material material) {
        //6、设置计划量
        switch (material.getAttr()) {
            case "purchasing":
                calculatePurchasingQuantity(productBomLevel, planQuantity, stockQuantity, planDemandQuantity, material);
                break;
            case "produce":
                calculateProduceQuantity(productBomLevel, planQuantity, stockQuantity, planDemandQuantity, material);
                break;
            case "outsourcing":
                calculateOutsourcingQuantity(productBomLevel, planQuantity, stockQuantity, planDemandQuantity, material);
                break;
            default:
                break;
        }
    }

    //计算采购数量
    private void calculatePurchasingQuantity(ProductBomLevel productBomLevel, Double planQuantity, Double stockQuantity, Double planDemandQuantity, Material material) {
        int times = TypeUtils.ceilNumber(planQuantity * 1.0 / material.getMinPackage());
        planQuantity = material.getMinPackage() * 1.0 * times;
        //计划量 planQuantity + 当天库存量（stockQuantity） > 最大库存量（maxStock），则按最大库存量计算
        if (planQuantity + stockQuantity - material.getMaxStock() > 0) {
            planQuantity = material.getMaxStock() - stockQuantity;
            times = TypeUtils.floorNumber(planQuantity * 1.0 / material.getMinPackage());
            planQuantity = material.getMinPackage() * 1.0 * times;
        }
        productBomLevel.setPlanQuantity(planQuantity);
        if (TypeUtils.ceilNumber(stockQuantity + planQuantity - planDemandQuantity) < 0) {
            String msg = String.format("背番: %s物料，因最大库存量(%s)限制,采购计划量加库存量(%s)不能满足需求量%s，则按满足最大库存量计划采购！", material.getBcode(), material.getMaxStock(), stockQuantity + planQuantity, planDemandQuantity);
            this.mrpLogBslService.buildMrpLog(1, msg);
            planDemandQuantity = stockQuantity + planQuantity;
        }
        //计划完成后更新库存
        this.stockDetailBslService.getStockMap().put(material.getBcode(), stockQuantity + planQuantity - planDemandQuantity);
    }

    //计算生产数量
    private void calculateProduceQuantity(ProductBomLevel productBomLevel, Double planQuantity, Double stockQuantity, Double planDemandQuantity, Material material) {
        if (planQuantity < material.getMinBatch()) {
            productBomLevel.setPlanQuantity(0);
            if (material.getBcode().equalsIgnoreCase("A-T115-1S")) {
                System.out.println("##############################" + material);
            }
            String msg;
            if (TypeUtils.ceilNumber(stockQuantity - planDemandQuantity) < 0) {
                msg = String.format("背番: %s物料，生产计划量小于最小批量，不能满足生产要求，但库存量小于需求量，计划与排程失败！", material.getBcode());
                this.mrpLogBslService.buildMrpLog(2, msg);
                throw new BusinessException("999999", msg);
            }else{
                msg = String.format("背番: %s物料，生产计划量小于最小批量，且在库量满足需求量%s，无需生产！", material.getBcode(),planDemandQuantity);
                this.mrpLogBslService.buildMrpLog(1, msg);
                this.stockDetailBslService.getStockMap().put(material.getBcode(), stockQuantity - planDemandQuantity);
            }
            return;
        }

        //按最小倍量生产
        int pTimes = TypeUtils.ceilNumber(planQuantity * 1.0 / material.getUseMultiple());
        planQuantity = material.getUseMultiple() * 1.0 * pTimes;
        //计划量 planQuantity + 当天库存量（stockQuantity） > 最大库存量（maxStock），则按最大库存量计算
        if (planQuantity + stockQuantity - material.getMaxStock() > 0) {
            planQuantity = material.getMaxStock() - stockQuantity;
            pTimes = TypeUtils.floorNumber(planQuantity * 1.0 / material.getUseMultiple());
            planQuantity = material.getUseMultiple() * 1.0 * pTimes;
        }
        productBomLevel.setPlanQuantity(planQuantity);

        if (TypeUtils.ceilNumber(stockQuantity + planQuantity - planDemandQuantity) < 0) {
            String msg = String.format("背番: %s物料，因最大库存量(%s)限制,计划生产量加库存量(%s)不能满足需求量%s，则按满足最大库存量计划排产！", material.getBcode(), material.getMaxStock(), stockQuantity + planQuantity, planDemandQuantity);
            this.mrpLogBslService.buildMrpLog(1, msg);
            planDemandQuantity = stockQuantity + planQuantity;
        }

        this.dealSubProductStock(productBomLevel, planQuantity);

        //计划完成后更新库存
        this.stockDetailBslService.getStockMap().put(material.getBcode(), stockQuantity + planQuantity - planDemandQuantity);
    }

    /**
     * 维护动态副产品库量
     *
     * @param productBomLevel 当前产品BOM层级
     * @param planQuantity    当前产品计划生产量
     */
    private void dealSubProductStock(ProductBomLevel productBomLevel, Double planQuantity) {
        List<ProductBomSub> productBomSubList = productBomLevel.getProductBomSubList();
        if (CollectionUtil.isNotEmpty(productBomSubList)) {
            Double finalPlanQuantity = planQuantity;
            productBomSubList.forEach(p -> {
                //副产品库存量
                Double tempStockQuantity = this.stockDetailBslService.getStockMap().get(p.getMaterialBCode());
                if (tempStockQuantity == null) {
                    tempStockQuantity = 0.0;
                }
                //副产品BOM倍量
                double quantity = p.getQuantity() == null ? 1.0 : p.getQuantity();
                //产成品倍数 = 计划量/产成品位量
                double quantityTimes = TypeUtils.ceilNumber(finalPlanQuantity * 100 / productBomLevel.getQuantity()) / 100.0;
                //副产品数量
                double subPlanQuantity = quantityTimes * quantity;
                this.stockDetailBslService.getStockMap().put(p.getMaterialBCode(), tempStockQuantity + subPlanQuantity);
            });
        }
    }

    //计算委外数量
    private void calculateOutsourcingQuantity(ProductBomLevel productBomLevel, Double planQuantity, Double stockQuantity, Double planDemandQuantity, Material material) {
        int pTimes;
        int oTimes = TypeUtils.ceilNumber(planQuantity * 1.0 / material.getUseMultiple());
        planQuantity = material.getUseMultiple() * 1.0 * oTimes;
        //计划量 planQuantity + 当天库存量（stockQuantity） > 最大库存量（maxStock），则按最大库存量计算
        if (planQuantity + stockQuantity - material.getMaxStock() > 0) {
            planQuantity = material.getMaxStock() - stockQuantity;
            if (planQuantity < 0) {
                String msg = String.format("背番: %s物料，最大库存量(%s),现有库存量%s，现有理论库存量已经超出最大库存量，因最大库存量限制，不能再继续委外排产！", material.getBcode(), material.getMaxStock(), stockQuantity);
                this.mrpLogBslService.buildMrpLog(2, msg);
                throw new BusinessException("999999", msg);
            }

            pTimes = TypeUtils.floorNumber(planQuantity * 1.0 / material.getUseMultiple());
            planQuantity = material.getUseMultiple() * 1.0 * pTimes;
        }
        productBomLevel.setPlanQuantity(material.getUseMultiple() * oTimes);
        if (TypeUtils.ceilNumber(stockQuantity + planQuantity - planDemandQuantity) < 0) {
            String msg = String.format("背番: %s物料，因最大库存量(%s)限制,委外计划量加库存量(%s)不能满足需求量%s，则按满足最大库存量委外排产！", material.getBcode(), material.getMaxStock(), stockQuantity + planQuantity, planDemandQuantity);
            this.mrpLogBslService.buildMrpLog(1, msg);
            planDemandQuantity = stockQuantity + planQuantity;
        }
        //计划完成后更新库存
        this.stockDetailBslService.getStockMap().put(material.getBcode(), stockQuantity + planQuantity - planDemandQuantity);
    }

    /**
     * 计算执行中（在制、在途、委外在途）
     *
     * @param deliveryStartDate 订单交货开始日期
     * @param deliveryEndDate   订单交货结束日期
     */
    private void calculateExecutionStock(LocalDate deliveryStartDate, LocalDate deliveryEndDate) {
        String startDate = DateUtils.getLocalDateFormat(deliveryStartDate, "yyyy-MM-dd 00:00:00");
        String endDate = DateUtils.getLocalDateFormat(deliveryEndDate, "yyyy-MM-dd 23:59:59");
        //查询主记录通用条件
        String condition = " isCreate = 0 and endDate >= '" + startDate + "' and endDate <= '" + endDate + "'";
        //计算库存及采购在制量
        this.calculateProductionStock(condition, startDate, endDate);
        //销售退货返修单数量
        this.calculateSaleRepairStock(startDate, endDate);
        //计算库存及采购在途量
        this.calculatePurchaseStock(condition, startDate, endDate);
        //计算库存及委外在途量
        this.calculateOutsourcingStock(condition, startDate, endDate);
    }

    /**
     * 计算库存及采购在制量
     *
     * @param condition 主记录查询条件
     * @param startDate 开始日期
     * @param endDate   结束日期
     */
    private void calculateProductionStock(String condition, String startDate, String endDate) {
        //生产计划订单交货数量（未下派工单）
        Map<String, Double> planMap = this.mrpBslService.fetchProductionPlanOrder(condition);
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), planMap);

        //生产工单计划交货数量
        String workOrderCondition = " status != 'finish' and status != 'abnormalFinish' and endDate >= '" + startDate + "' and endDate <= '" + endDate + "'";
        Map<String, Double> workOrderMap = this.mrpBslService.fetchWorkOrder(workOrderCondition, "workOrder");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), workOrderMap);

        //生产报工数量  -(不良+报工）
        String workReportCondition = " status != 'finish' and  parentId = '4ja1eoa9f6o000' and workReportEndTime >= '" + startDate + "' and workReportEndTime <= '" + endDate + "'";
        Map<String, Double> workReportMap = this.mrpBslService.fetchWorkReport(workReportCondition, "workReport");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), workReportMap);

        //返修单在制统计
        String repairWorkReportCondition = " isApproved != 1 and workReportDate >= '" + startDate + "' and workReportDate <= '" + endDate + "'";
        Map<String, Double> repairWorkOrderMap = this.mrpBslService.fetchRepairWorkReport(repairWorkReportCondition, "repairWorkReport");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), repairWorkOrderMap);
    }

    /**
     * 计算库存及销售在制量
     *
     * @param startDate 开始日期
     * @param endDate   结束日期
     */
    private void calculateSaleRepairStock(String startDate, String endDate) {
        // 销售退货返修单数量
        String saleRepairWorkOrderCondition = " isApproved != 1 ";
        String saleHierRepairWorkOrderCondition = " parentId = 'v5bmai28yts00' and workReportDate >= '" + startDate + "' and workReportDate <= '" + endDate + "'";
        Map<String, Double> saleRepairWorkOrderMap = this.mrpBslService.fetchSaleRepairWorkOrder(saleRepairWorkOrderCondition, saleHierRepairWorkOrderCondition, "saleReturnWorkOrder");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), saleRepairWorkOrderMap);

        //销售退货返修报工  -(不良-返修报工数)
        String saleRepairWorkReportCondition = " isApproved != 1 ";
        String saleHierRepairWorkReportCondition = " parentId = 'bptjy2wl0qo00' and workReportDate >= '" + startDate + "' and workReportDate <= '" + endDate + "'";
        Map<String, Double> saleRepairWorkReportMap = this.mrpBslService.fetchSaleRepairWorkReport(saleRepairWorkReportCondition, saleHierRepairWorkReportCondition, "saleReturnWorkReport");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), saleRepairWorkReportMap);
    }

    /**
     * 计算库存及采购在途量
     *
     * @param condition 主记录查询条件
     * @param startDate 开始日期
     * @param endDate   结束日期
     */
    private void calculatePurchaseStock(String condition, String startDate, String endDate) {
        //采购到货数量
        Map<String, Double> purchaseMap = this.mrpBslService.fetchPurchasePlanOrder(condition);
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), purchaseMap);

        //采购订单在途量
        String purchaseOrderCondition = " status != 9 and arrivalDate >= '" + startDate + "' and arrivalDate <= '" + endDate + "'";
        String purchaseOrderHierCondition = " parentId = 'a7bgmkl8f8000' ";
        Map<String, Double> purchaseOrderSubMap = this.mrpBslService.fetchPurchaseOrderSub(purchaseOrderCondition, purchaseOrderHierCondition, "PurchaseOrder");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), purchaseOrderSubMap);

        //采购补货:【采购补货单（未完结）-采购补货入库（已审核）】；
        String purchaseReplenishCondition = " status != 9 and date >= '" + startDate + "' and date <= '" + endDate + "'";
        String purchaseReplenishHierCondition = " parentId = 'a7bgmkl8f8000' ";
        Map<String, Double> purchaseReplenishSubMap = this.mrpBslService.fetchPurchaseOrderSub(purchaseReplenishCondition, purchaseReplenishHierCondition, "PurchaseReplenish");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), purchaseReplenishSubMap);
    }

    /**
     * 计算库存及委外在途量
     *
     * @param condition 主记录查询条件
     * @param startDate 开始日期
     * @param endDate   结束日期
     */
    private void calculateOutsourcingStock(String condition, String startDate, String endDate) {
        //委外到货数量
        Map<String, Double> outsourcingMap = this.mrpBslService.fetchOutsourcingPlanOrder(condition);
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), outsourcingMap);

        String outsourcingOrderCondition = " status != 9 and endDate >= '" + startDate + "' and endDate <= '" + endDate + "'";
        String outsourcingOrderHierCondition = " parentId = '220hbsn5qwhs00' ";
        Map<String, Double> outsourcingOrderSubMap = this.mrpBslService.fetchOutsourcingOrderSub(outsourcingOrderCondition, outsourcingOrderHierCondition, "outsourcingOrder");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), outsourcingOrderSubMap);

        //采购补货:【采购补货单（未完结）-采购补货入库（已审核）】；
        String outsourcingReplenishCondition = " status != 9 and replenishDate >= '" + startDate + "' and replenishDate <= '" + endDate + "'";
        String outsourcingReplenishHierCondition = " parentId = 'root' ";
        Map<String, Double> outsourcingReplenishSubMap = this.mrpBslService.fetchOutsourcingOrderSub(outsourcingReplenishCondition, outsourcingReplenishHierCondition, "outsourcingReplenish");
        this.mrpBslService.mergedMap(this.stockDetailBslService.getStockMap(), outsourcingReplenishSubMap);
    }

    /**
     * 删除未所定计划订单
     */
    private void deletePlanOrder() {
        this.deleteOutsourcingPlanOrder();
        this.deletePurchasePlanOrder();
        this.deleteProductionPlanOrder();
    }

    /**
     * 删除未锁定的委外生产计划订单
     */
    private void deleteOutsourcingPlanOrder() {
        try {
            String condition = " isLock != 1 "; //未被锁定的全部清理掉
            String className = "OutsourcingPlanOrder";
            List<LogicInstance> logicInstances = this.logicInstanceDslService.fetchInstanceList(condition, className, null);
            List<Long> ids = logicInstances.stream().map(LogicInstance::getId).collect(toList());
            if (CollectionUtil.isNotEmpty(ids)) {
                this.logicInstanceDslService.deleteByIds(ids);
            }
        } catch (Exception ex) {
            this.mrpLogBslService.buildMrpLog(2, "删除未锁定的委外订单报错!");
            ex.printStackTrace();
            throw new BusinessException("999999", "删除未锁定的委外订单报错!", ex);
        }
    }

    /**
     * 删除未锁定的采购计划订单
     */
    private void deletePurchasePlanOrder() {
        try {
            String condition = " isLock != 1 "; //未被锁定的全部清理掉
            String className = "PurchasePlanOrder";
            List<LogicInstance> logicInstances = this.logicInstanceDslService.fetchInstanceList(condition, className, null);
            List<Long> ids = logicInstances.stream().map(LogicInstance::getId).collect(toList());
            if (CollectionUtil.isNotEmpty(ids)) {
                this.logicInstanceDslService.deleteByIds(ids);
            }
        } catch (Exception ex) {
            this.mrpLogBslService.buildMrpLog(2, "删除未锁定的采购计划订单报错!");
            throw new BusinessException("999999", "删除未锁定的采购计划订单报错!", ex);
        }
    }

    /**
     * 删除未锁定的生产计划订单
     */
    private void deleteProductionPlanOrder() {
        try {
            String condition = " isLock != 1 "; //未被锁定的全部清理掉
            String className = "ProductionPlanOrder";
            List<LogicInstance> logicInstances = this.logicInstanceDslService.fetchInstanceList(condition, className, null);
            List<Long> ids = logicInstances.stream().map(LogicInstance::getId).collect(toList());
            if (CollectionUtil.isNotEmpty(ids)) {
                this.logicInstanceDslService.deleteByIds(ids);
            }
        } catch (Exception ex) {
            this.mrpLogBslService.buildMrpLog(2, "删除未锁定的生产计划订单报错!");
            throw new BusinessException("999999", "删除未锁定的生产计划订单报错!", ex);
        }
    }

    /**
     * 合并采购计划订单，并生成最终的采购计划
     */
    private void combinedPurchasePlanOrder(List<String> ids, int period) {
        String joinIds = String.join(",", ids);
        String condition = String.format(" id in (%s)", joinIds);
        this.mrpBslService.fetchPurchasePlanOrder(condition);
        List<PurchasePlanOrder> purchasePlanOrders = this.mrpBslService.getPurchasePlanOrders();
        if (CollectionUtil.isEmpty(purchasePlanOrders)) {
            this.mrpLogBslService.buildMrpLog(2, "没查询到采购计划订单！");
            throw new BusinessException("999999", "没查询到采购计划订单");
        }

        PurchasePlanOrder purchasePlanOrder = purchasePlanOrders.stream().filter(p -> !p.isLocked()).findFirst().orElse(null);
        if (purchasePlanOrder != null) {
            String msg = "所选采购计划订单存在未被锁定的，合并计划失败!";
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        Map<String, List<PurchasePlanOrder>> planOrderMap = this.buildPurchasePlanOrderMap(purchasePlanOrders, period);
        for (Map.Entry<String, List<PurchasePlanOrder>> entry : planOrderMap.entrySet()) {
            List<PurchasePlanOrder> planOrders = entry.getValue();
            Date endDateMax = planOrders.stream().map(PurchasePlanOrder::getEndDate).max(Date::compareTo).get();
            Date startDateMin = planOrders.stream().map(PurchasePlanOrder::getStartDate).min(Date::compareTo).get();
            String endDate = DateUtils.getDateFormat(endDateMax, "yyyy-MM-dd 00:00:00");
            String startDate = DateUtils.getDateFormat(startDateMin, "yyyy-MM-dd 00:00:00");
            String date = DateUtils.getDateFormat(new Date(), "yyyy-MM-dd 00:00:00");
            List<String> codeList = planOrders.stream().filter(p -> StringUtils.isNotBlank(p.getOrderCode())).map(PurchasePlanOrder::getOrderCode).collect(toList());
            String orderCodes = String.join(",", codeList);

            List<String> mrpIdList = planOrders.stream().filter(p -> p.getMrpId() != null).map(PurchasePlanOrder::getMrpId).map(m -> m + "").collect(toList());
            String mrpIds = String.join(",", mrpIdList);

            JSONObject planValues = new JSONObject();
            String name = String.format("%s的采购计划", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"));
            int year = LocalDate.now().getYear();
            String planCode = String.format("G_PLAN_%s", year);
            planValues.put("name", name);
            planValues.put("code", batchNumberDslService.getNextVal(planCode));
            planValues.put("date", date);
            planValues.put("orderDate", startDate);
            planValues.put("arrivalDate", endDate);
            planValues.put("orderCode", orderCodes);
            planValues.put("status", 0);
            planValues.put("mrpId", mrpIds);
            JSONObject mainPlan = this.logicInstanceDslService.add(ModelType.Logic.getCode(), "PurchasePlan", name, planValues);
            int mainPlanId = mainPlan.getInteger("id");
            Map<String, Integer> tempSubMap = planOrders.stream().collect(Collectors.groupingBy(PurchasePlanOrder::getMaterialBCode, Collectors.summingInt(PurchasePlanOrder::getQuantity)));
            for (Map.Entry<String, Integer> subEntry : tempSubMap.entrySet()) {
                String bCode = subEntry.getKey();
                Integer value = subEntry.getValue();
                Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(bCode)).findFirst().orElse(null);
                Double conversionRate = material.getConversionRate();
                int subQuantity;
                if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
                    subQuantity = 1;
                    conversionRate = value * 1.0;
                } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
                    subQuantity = TypeUtils.ceilNumber(value / conversionRate);
                } else {
                    throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常！", material.getBcode()));
                }

                JSONObject values = new JSONObject();
                values.put("material_id", material.getId());
                values.put("material_name", material.getName());
                values.put("material_pcode", material.getPcode());
                values.put("material_bcode", material.getBcode());
                values.put("material_spec", material.getSpec());
                values.put("material_model", material.getModel());
                values.put("material_unit", material.getUnit());
                values.put("material_subUnit", material.getSubUnit());
                values.put("supplier_id", material.getSupplierId());
                values.put("supplier_name", material.getSupplierName());
                values.put("supplier_code", material.getSupplierCode());
                values.put("conversionRate", conversionRate);
                values.put("quantity", value);
                values.put("subQuantity", subQuantity);
                values.put("price", material.getReferToPrice());
                values.put("startDate", startDate);
                values.put("endDate", endDate);
                values.put("orderCode", orderCodes);

                JSONObject hierInstanceJson = new JSONObject();
                hierInstanceJson.put("parentId", "4b9r0inoqyvwg0");
                hierInstanceJson.put("containerId", mainPlanId);
                hierInstanceJson.put("containerModel", ModelType.Logic.getCode());
                hierInstanceJson.put("className", "PurchasePlan");
                hierInstanceJson.put("values", values);
                this.hierInstanceDslService.addChild(hierInstanceJson);
            }
        }
    }

    /**
     * 构建采购计划订单M Map<String, List<PurchasePlanOrder>>
     *
     * @param purchasePlanOrders 所有采购计划订单
     * @param period             合并周期
     * @return 返回map 结果
     */
    private Map<String, List<PurchasePlanOrder>> buildPurchasePlanOrderMap(List<PurchasePlanOrder> purchasePlanOrders, int period) {
        Date endDateMin = purchasePlanOrders.stream().map(PurchasePlanOrder::getEndDate).min(Date::compareTo).get();
        List<PurchasePlanOrder> planOrderList = purchasePlanOrders.stream()
                .sorted(Comparator.comparing(PurchasePlanOrder::getEndDate))
                .collect(toList());
        //循环次数
        int index = 1;
        Map<String, List<PurchasePlanOrder>> planOrderMap = new HashMap<>();
        //最小交期转换，取最小交期作为合并订单后的交期，需要取前一天用来比较
        LocalDate endLocalDateMin = DateUtils.ConvertDateToLocalDate(endDateMin);
        LocalDate endLocalDateMax = endLocalDateMin.plusDays(index * period);
        final LocalDate finalDeliveryLocalDateMax = endLocalDateMax.plusDays(1);
        Date calcEndDate = purchasePlanOrders.stream().filter(p -> DateUtils.ConvertDateToLocalDate(p.getEndDate()).isBefore(finalDeliveryLocalDateMax)).map(PurchasePlanOrder::getEndDate).max(Date::compareTo).orElse(null);
        String strEndDate = DateUtils.getDateFormat(calcEndDate, "yyyyMMdd");
        planOrderMap.put(strEndDate, new ArrayList<>());
        for (PurchasePlanOrder order : planOrderList) {
            LocalDate theOrderDate = DateUtils.ConvertDateToLocalDate(order.getEndDate());
            if (theOrderDate.isAfter(endLocalDateMax)) {
                long days = ChronoUnit.DAYS.between(endLocalDateMin, theOrderDate);
                index = (int) Math.ceil(days * 1.0 / period);
                LocalDate tempLocalDateMax = endLocalDateMin.plusDays(index * period);
                LocalDate tempLocalDate = tempLocalDateMax.plusDays(1);
                //下一个周期的最小计划交货期
                final LocalDate finalDeliveryLocalDateMin = endLocalDateMax;
                calcEndDate = purchasePlanOrders.stream().filter(p -> DateUtils.ConvertDateToLocalDate(p.getEndDate()).isAfter(finalDeliveryLocalDateMin) && DateUtils.ConvertDateToLocalDate(p.getEndDate()).isBefore(tempLocalDate)).map(PurchasePlanOrder::getEndDate).max(Date::compareTo).orElse(null);
                strEndDate = DateUtils.getDateFormat(calcEndDate, "yyyyMMdd");
                planOrderMap.put(strEndDate, new ArrayList<>());
                endLocalDateMax = tempLocalDateMax;
            }
            planOrderMap.get(strEndDate).add(order);
        }
        return planOrderMap;
    }

    /**
     * 合并生产计划订单到工单
     *
     * @param ids    生产计划订单id
     * @param period 合并周期
     */
    private void combinedProductionPlanOrderToWorkOrder2(List<String> ids, int period) {
        String joinIds = String.join(",", ids);
        String condition = String.format(" id in (%s)", joinIds);
        this.mrpBslService.fetchProductionPlanOrder(condition);
        List<ProductionPlanOrder> productionPlanOrders = this.mrpBslService.getProductionPlanOrders();
        if (CollectionUtil.isEmpty(productionPlanOrders)) {
            String msg = "没查询到生产计划订单!";
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }
        ProductionPlanOrder productionPlanOrder = productionPlanOrders.stream().filter(p -> !p.isLocked()).findFirst().orElse(null);
        if (productionPlanOrder != null) {
            String msg = "所选生产计划订单存在未被锁定的，生成工单失败!";
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        for (ProductionPlanOrder planOrder : productionPlanOrders) {
            Date planStartDate = planOrder.getStartDate();
            Date planEndDate = planOrder.getEndDate();
            String startDate = DateUtils.getLocalDateTimeFormat(planStartDate, "yyyy-MM-dd HH:mm:ss");
            String endDate = DateUtils.getLocalDateTimeFormat(planEndDate, "yyyy-MM-dd HH:mm:ss");
            Integer quantity = planOrder.getQuantity();
            Double workHours = planOrder.getWorkHours();

            String materialBCode = planOrder.getMaterialBCode();
            Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
            Double conversionRate = material.getConversionRate();
            // 获取辅助数量
            double subQuantity = getSubQuantity(material, quantity);
            JSONObject values = getWppEquipInfo(materialBCode);
            String orderCode = planOrder.getOrderCode();
            String name = String.format("%s的生产计划", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"), material.getBcode());
            int year = LocalDate.now().getYear();
            String planCode = String.format("P_WO_%s", year);
            String trialCondition = " deliveryDate >='" + startDate + "' AND isApproved == 1 AND isTrial=1";
            saleOrderBslService.initTrialOrder(trialCondition);
            List<SaleOrderSub> trialOrderSubs = saleOrderBslService.getTrialOrderSubs();
            SaleOrderSub saleOrderSub = trialOrderSubs.stream().filter(o -> o.getMaterialBCode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
            boolean trialStatus = false;
            if (saleOrderSub != null) {
                trialStatus = true;
            }

            values.put("name", name);
            values.put("planId", planOrder.getId());
            values.put("planNo", planOrder.getCode());
            values.put("orderNumber", batchNumberDslService.getNextVal(planCode));
            values.put("orderType", StringUtils.isNotBlank(orderCode) ? "订单" : "内示");
            values.put("orderCode", orderCode);
            values.put("routingNumber", "");
            values.put("startDate", startDate);
            values.put("endDate", endDate);
            values.put("workHours", workHours);
            values.put("material_pcode", material.getPcode());
            values.put("material_bcode", material.getBcode());
            values.put("material_subUnit", material.getSubUnit());
            values.put("quantity", quantity);
            values.put("subQuantity", subQuantity);
            values.put("conversionRate", conversionRate);
            values.put("material_spec", material.getSpec());
            values.put("material_model", material.getModel());
            values.put("carModel", material.getCarModel());
            values.put("status", "init");
            values.put("trialStatus", trialStatus);
            JSONObject productionPlan = this.logicInstanceDslService.add(ModelType.Logic.getCode(), "workOrder", name, values);

            Map<String, Double> materialMap = new HashMap<>();
            Map<String, Double> productMap = new HashMap<>();
            //构造产品的原料和副产品信息
            buildProductMap(materialBCode, materialMap, quantity, productMap);

            Integer mainPlanId = productionPlan.getInteger("id");
            for (Map.Entry<String, Double> subEntry : productMap.entrySet()) {
                this.saveWorkOrderHierInstance2(subEntry, "2", mainPlanId);
            }

            for (Map.Entry<String, Double> subEntry : materialMap.entrySet()) {
                this.saveWorkOrderHierInstance2(subEntry, "1", mainPlanId);
            }
        }
    }

    /**
     * 保存工单物料或副产品明细
     *
     * @param entry
     * @param subType
     * @param mainPlanId
     */
    private void saveWorkOrderHierInstance2(Map.Entry<String, Double> entry,String subType, Integer mainPlanId) {
        String materialBCode = entry.getKey();
        Integer quantity = entry.getValue().intValue();
        Material tempMaterial = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        Double conversionRate = tempMaterial.getConversionRate();
        double subQuantity = getSubQuantity(tempMaterial, quantity);
        JSONObject hierInstanceJson = new JSONObject();
        hierInstanceJson.put("parentId", "458qninw7j8000");
        hierInstanceJson.put("containerId", mainPlanId);
        hierInstanceJson.put("containerModel", ModelType.Logic.getCode());
        hierInstanceJson.put("className", "workOrder");
        JSONObject values = new JSONObject();
        values.put("material_id", tempMaterial.getId());
        values.put("material_pcode", tempMaterial.getPcode());
        values.put("material_bcode", tempMaterial.getBcode());
        values.put("material_spec", tempMaterial.getSpec());
        values.put("material_model", tempMaterial.getModel());
        values.put("material_unit", tempMaterial.getUnit());
        values.put("material_subUnit", tempMaterial.getSubUnit());
        values.put("subType", subType);  //1:原料料，2：副产品
        values.put("quantity", quantity);
        values.put("subQuantity", subQuantity);
        values.put("conversionRate", conversionRate);
        hierInstanceJson.put("values", values);
        this.hierInstanceDslService.addChild(hierInstanceJson);
    }


    private double getSubQuantity(Material material,Integer quantity){
        Integer acceptQuantity = material.getAcceptQuantity();
        Double conversionRate = material.getConversionRate();
        double subQuantity;
        if (acceptQuantity != null && TypeUtils.ceilNumber((double) (acceptQuantity * 100)) / 100 > 0) {
            subQuantity = TypeUtils.ceilNumber(quantity * 1.0 / acceptQuantity);
            return subQuantity;
        }
        if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
            subQuantity = 1;
        } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
            subQuantity = TypeUtils.ceilNumber(quantity * 1.0 / conversionRate);
        } else {
            throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常，合并生成生产工单失败！", material.getBcode()));
        }
        return subQuantity;
    }

    /**
     * 获取工单工艺路线及设备相关信息
     *
     * @param materialBCode 物料背番
     * @return 返回工单工艺路线及设备相关信息
     */
    private JSONObject getWppEquipInfo(String materialBCode) {
        JSONObject wppJson = workProcessPathBslService.getWppEquipList(materialBCode);
        JSONObject values = new JSONObject();
        if (wppJson != null) {
            values.put("equipIds", wppJson.get("equipIds"));
            values.put("equipCodes", wppJson.get("equipCodes"));
            values.put("equipNames", wppJson.get("equipNames"));
            values.put("wppId", wppJson.getLong("wppId"));
            values.put("wppCode", wppJson.get("wppCode"));
            values.put("wppName", wppJson.get("wppName"));
            values.put("fixtureCodes", wppJson.get("fixtureCodes"));
            values.put("mouldCods", wppJson.get("mouldCods"));
            values.put("Document_id", wppJson.getInteger("documentId"));
            values.put("bomCode", wppJson.getString("bomCode"));
            values.put("bomType", wppJson.getString("bomType"));
            values.put("lineBody", wppJson.getString("lineBodyCode"));
            values.put("lineBodyName", wppJson.getString("lineBodyName"));
        }
        return values;
    }

    /**
     * 合并生产计划订单到工单
     *
     * @param ids    生产计划订单id
     * @param period 合并周期
     */
    private void combinedProductionPlanOrderToWorkOrder(List<String> ids, int period) {
        String joinIds = String.join(",", ids);
        String condition = String.format(" id in (%s)", joinIds);
        this.mrpBslService.fetchProductionPlanOrder(condition);
        List<ProductionPlanOrder> productionPlanOrders = this.mrpBslService.getProductionPlanOrders();
        if (CollectionUtil.isEmpty(productionPlanOrders)) {
            String msg = "没查询到生产计划订单!";
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }
        ProductionPlanOrder productionPlanOrder = productionPlanOrders.stream().filter(p -> !p.isLocked()).findFirst().orElse(null);
        if (productionPlanOrder != null) {
            String msg = "所选生产计划订单存在未被锁定的，合并计划失败!";
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        Map<String, Map<Integer, List<ProductionPlanOrder>>> productPlanOrderMap = buildProductionPlanOrderMap(productionPlanOrders, period);
        for (Map.Entry<String, Map<Integer, List<ProductionPlanOrder>>> entry : productPlanOrderMap.entrySet()) {
            String bCode = entry.getKey();
            Map<Integer, List<ProductionPlanOrder>> planOrderMap = entry.getValue();
            for (Map.Entry<Integer, List<ProductionPlanOrder>> tempEntry : planOrderMap.entrySet()) {
                List<ProductionPlanOrder> planOrders = tempEntry.getValue();
                Date planStartDate = planOrders.stream().map(ProductionPlanOrder::getStartDate).min(Date::compareTo).get();
                Date planEndDate = planOrders.stream().map(ProductionPlanOrder::getEndDate).max(Date::compareTo).get();

                String startDate = DateUtils.getDateFormat(planStartDate, "yyyy-MM-dd 00:00:00");
                String endDate = DateUtils.getDateFormat(planEndDate, "yyyy-MM-dd 00:00:00");

                Integer quantity = planOrders.stream().collect(summingInt(ProductionPlanOrder::getQuantity));

                List<ProductionPlanOrderSub> planOrderSubs = new ArrayList<>();
                Map<String, Integer> tempSubMap = this.mrpBslService.fetchProductionPlanOrderMap(planOrders, planOrderSubs);

                Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(bCode)).findFirst().orElse(null);
                Double conversionRate = material.getConversionRate();
                double subQuantity;
                if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
                    subQuantity = 1;
                    conversionRate = quantity * 1.0;
                } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
                    subQuantity = TypeUtils.ceilNumber(quantity * 1.0 / conversionRate);
                } else {
                    throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常，合并生成委外计划失败！", material.getBcode()));
                }
                JSONObject values = getWppEquipInfo(bCode);

                List<String> codeList = planOrders.stream().filter(p -> StringUtils.isNotBlank(p.getOrderCode())).map(ProductionPlanOrder::getOrderCode).collect(toList());
                String orderCodes = String.join(",", codeList);

                String name = String.format("%s的生产计划", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"), material.getBcode());
                int year = LocalDate.now().getYear();
                String planCode = String.format("P_WO_%s", year);

                String trialCondition = " deliveryDate >='" + startDate + "' AND isApproved == 1 AND isTrial=1";
                saleOrderBslService.initTrialOrder(trialCondition);
                List<SaleOrderSub> trialOrderSubs = saleOrderBslService.getTrialOrderSubs();
                SaleOrderSub saleOrderSub = trialOrderSubs.stream().filter(o -> o.getMaterialBCode().equalsIgnoreCase(bCode)).findFirst().orElse(null);
                boolean trialStatus = false;
                if (saleOrderSub != null) {
                    trialStatus = true;
                }

                values.put("name", name);
                values.put("orderNumber", batchNumberDslService.getNextVal(planCode));
                values.put("orderType", StringUtils.isNotBlank(orderCodes) ? "订单" : "内示");
                values.put("orderCode", orderCodes);
                values.put("routingNumber", "");
                values.put("startDate", startDate);
                values.put("endDate", endDate);
                values.put("material_pcode", material.getPcode());
                values.put("material_bcode", material.getBcode());
                values.put("material_subUnit", material.getSubUnit());
                values.put("quantity", quantity);
                values.put("subQuantity", subQuantity);
                values.put("conversionRate", conversionRate);
                values.put("material_spec", material.getSpec());
                values.put("material_model", material.getModel());
                values.put("carModel", material.getCarModel());
                values.put("status", "init");
                values.put("trialStatus", trialStatus);
                JSONObject productionPlan = this.logicInstanceDslService.add(ModelType.Logic.getCode(), "workOrder", name, values);
                Integer mainPlanId = productionPlan.getInteger("id");
                for (Map.Entry<String, Integer> subEntry : tempSubMap.entrySet()) {
                    this.saveWorkOrderHierInstance(subEntry, planOrderSubs, mainPlanId);
                }
            }
        }
    }

    /**
     * 保存工单物料或副产品明细
     *
     * @param entry
     * @param planOrderSubs
     * @param mainPlanId
     */
    private void saveWorkOrderHierInstance(Map.Entry<String, Integer> entry, List<ProductionPlanOrderSub> planOrderSubs, Integer mainPlanId) {
        String materialBCode = entry.getKey();
        Integer quantity = entry.getValue();
        ProductionPlanOrderSub productionPlanOrderSub = planOrderSubs.stream().filter(po -> po.getMaterialBCode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        String subType = productionPlanOrderSub.getSubType();
        if (StringUtils.isBlank(subType)) {
            subType = "1";
        }
        Material tempMaterial = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(materialBCode)).findFirst().orElse(null);
        Double conversionRate = tempMaterial.getConversionRate();
        double subQuantity;
        if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
            subQuantity = 1;
            conversionRate = quantity * 1.0;
        } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
            subQuantity = TypeUtils.ceilNumber(quantity * 1.0 / conversionRate);
        } else {
            throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常，合并生成委外计划失败！", materialBCode));
        }
        JSONObject hierInstanceJson = new JSONObject();
        hierInstanceJson.put("parentId", "458qninw7j8000");
        hierInstanceJson.put("containerId", mainPlanId);
        hierInstanceJson.put("containerModel", ModelType.Logic.getCode());
        hierInstanceJson.put("className", "workOrder");
        JSONObject values = new JSONObject();
        values.put("material_id", tempMaterial.getId());
        values.put("material_pcode", tempMaterial.getPcode());
        values.put("material_bcode", tempMaterial.getBcode());
        values.put("material_spec", tempMaterial.getSpec());
        values.put("material_model", tempMaterial.getModel());
        values.put("material_unit", tempMaterial.getUnit());
        values.put("material_subUnit", tempMaterial.getSubUnit());
        values.put("subType", subType);
        values.put("quantity", quantity);
        values.put("subQuantity", subQuantity);
        values.put("conversionRate", conversionRate);
        hierInstanceJson.put("values", values);
        this.hierInstanceDslService.addChild(hierInstanceJson);
    }

    /**
     * 构建生产计划订单M Map<String, Map<String, List<ProductionPlanOrder>>>
     *
     * @param productionPlanOrders 所有采购计划订单
     * @param period               合并周期
     * @return 返回map 结果
     */
    private Map<String, Map<Integer, List<ProductionPlanOrder>>> buildProductionPlanOrderMap(List<ProductionPlanOrder> productionPlanOrders, int period) {
        //间隔豪秒数1 day = 24 hours * 60 minutes * 60 seconds * 1000 milliseconds = 86400000 milliseconds
        int periodMillis = period * 86400000;
        Map<String, Map<Integer, List<ProductionPlanOrder>>> productPlanOrderMap = new HashMap<>();
        Map<String, List<ProductionPlanOrder>> planOrderMap = productionPlanOrders.stream().collect(groupingBy(ProductionPlanOrder::getMaterialBCode));
        for (Map.Entry<String, List<ProductionPlanOrder>> entry : planOrderMap.entrySet()) {
            String bCode = entry.getKey();
            List<ProductionPlanOrder> tempPlanOrderList = entry.getValue();
            Map<Integer, List<ProductionPlanOrder>> planOrderDateMap = tempPlanOrderList.stream().collect(Collectors.groupingBy(obj -> (int) Math.ceil(obj.getEndDate().getTime() * 1.0 / periodMillis)));
            productPlanOrderMap.put(bCode, planOrderDateMap);
        }

        return productPlanOrderMap;
    }

    private void combinedOutsourcingPlanOrder(List<String> ids, int period) {
        String joinIds = String.join(",", ids);
        String condition = String.format(" id in (%s)", joinIds);
        this.mrpBslService.fetchOutsourcingPlanOrder(condition);
        List<OutsourcingPlanOrder> outsourcingPlanOrders = this.mrpBslService.getOutsourcingPlanOrders();
        if (CollectionUtil.isEmpty(outsourcingPlanOrders)) {
            this.mrpLogBslService.buildMrpLog(2, "没查询到委外计划订单!");
            throw new BusinessException("999999", "没查询到委外计划订单");
        }

        OutsourcingPlanOrder outsourcingPlanOrder = outsourcingPlanOrders.stream().filter(p -> !p.isLocked()).findFirst().orElse(null);
        if (outsourcingPlanOrder != null) {
            String msg = "所选委外计划订单存在未被锁定的，合并计划失败!";
            this.mrpLogBslService.buildMrpLog(2, msg);
            throw new BusinessException("999999", msg);
        }

        //间隔豪秒数1 day = 24 hours * 60 minutes * 60 seconds * 1000 milliseconds = 86400000 milliseconds
        int periodMillis = period * 86400000;

        Map<Integer, List<OutsourcingPlanOrder>> planOrderMap = outsourcingPlanOrders.stream().collect(Collectors.groupingBy(obj -> (int) Math.ceil(obj.getEndDate().getTime() * 1.0 / periodMillis)));
        for (Map.Entry<Integer, List<OutsourcingPlanOrder>> entry : planOrderMap.entrySet()) {
            List<OutsourcingPlanOrder> planOrders = entry.getValue();
            Date planStartDate = planOrders.stream().map(OutsourcingPlanOrder::getStartDate).min(Date::compareTo).get();
            Date planEndDate = planOrders.stream().map(OutsourcingPlanOrder::getEndDate).max(Date::compareTo).get();
            String startDate = DateUtils.getDateFormat(planStartDate, "yyyy-MM-dd 00:00:00");
            String endDate = DateUtils.getDateFormat(planEndDate, "yyyy-MM-dd 00:00:00");
            String date = DateUtils.getDateFormat(new Date(), "yyyy-MM-dd 00:00:00");
            List<String> codeList = planOrders.stream().filter(p -> StringUtils.isNotBlank(p.getOrderCode())).map(OutsourcingPlanOrder::getOrderCode).distinct().collect(toList());
            String orderCodes = String.join(",", codeList);
            List<String> mrpIdList = planOrders.stream().filter(p -> p.getMrpId() != null).map(OutsourcingPlanOrder::getMrpId).map(m -> m + "").collect(toList());
            String mrpIds = String.join(",", mrpIdList);

            JSONObject planValues = new JSONObject();
            String name = String.format("%s的委外计划", DateUtils.getDateFormat(new Date(), "yyyy年MM月dd日"));
            int year = LocalDate.now().getYear();
            String planCode = String.format("O_PLAN_%s", year);
            planValues.put("name", name);
            planValues.put("code", batchNumberDslService.getNextVal(planCode));
            planValues.put("date", date);
            planValues.put("startDate", startDate);
            planValues.put("endDate", endDate);
            planValues.put("orderCode", orderCodes);
            planValues.put("status", 0);
            planValues.put("mrpId", mrpIds);
            JSONObject mainPlan = this.logicInstanceDslService.add(ModelType.Logic.getCode(), "OutsourcingPlan", name, planValues);
            Integer mainPlanId = mainPlan.getInteger("id");
            Map<String, Integer> outsourcingMap = planOrders.stream().collect(Collectors.groupingBy(OutsourcingPlanOrder::getMaterialBCode, Collectors.summingInt(OutsourcingPlanOrder::getQuantity)));
            for (Map.Entry<String, Integer> tempEntry : outsourcingMap.entrySet()) {
                String bCode = tempEntry.getKey();
                Integer value = tempEntry.getValue();
                Material material = this.materialBslService.getMaterials().stream().filter(m -> m.getBcode().equalsIgnoreCase(bCode)).findFirst().orElse(null);
                if (material == null) {
                    this.mrpLogBslService.buildMrpLog(2, "没有维护物料信息生成委外计划失败！");
                    throw new BusinessException("999999", "没有维护物料信息生成委外计划失败！");
                }
                Double conversionRate = material.getConversionRate();
                int subQuantity;
                if (conversionRate == null || TypeUtils.ceilNumber(conversionRate * 100) / 100 == 0) {
                    subQuantity = 1;
                    conversionRate = value * 1.0;
                } else if (TypeUtils.ceilNumber(conversionRate * 100) / 100 > 0) {
                    subQuantity = TypeUtils.ceilNumber(value / conversionRate);
                } else {
                    throw new BusinessException("999999", String.format("背番：%s物料单位转换率为0，主数量除以转换率（0）异常！", material.getBcode()));
                }

                JSONObject values = new JSONObject();
                values.put("material_id", material.getId());
                values.put("material_name", material.getName());
                values.put("material_pcode", material.getPcode());
                values.put("material_bcode", material.getBcode());
                values.put("material_spec", material.getSpec());
                values.put("material_model", material.getModel());
                values.put("material_unit", material.getUnit());
                values.put("material_subUnit", material.getSubUnit());
                values.put("supplier_id", material.getOutsourcingSupplierId());
                values.put("supplier_name", material.getOutsourcingSupplierName());
                values.put("supplier_code", material.getOutsourcingSupplierCode());
                values.put("quantity", value);
                values.put("conversionRate", conversionRate);
                values.put("subQuantity", subQuantity);
                values.put("price", material.getReferToPrice());
                values.put("startDate", startDate);
                values.put("endDate", endDate);
                values.put("orderCode", orderCodes);

                JSONObject hierInstanceJson = new JSONObject();
                hierInstanceJson.put("parentId", "root");
                hierInstanceJson.put("containerId", mainPlanId);
                hierInstanceJson.put("containerModel", ModelType.Logic.getCode());
                hierInstanceJson.put("className", "OutsourcingPlan");
                hierInstanceJson.put("values", values);
                this.hierInstanceDslService.addChild(hierInstanceJson);
            }
        }
    }
}
